关于spring-security:springBoot整合spring-security实现权限管理单体应用版筑基初期

写在后面

在后面的学习当中,咱们对spring security有了一个小小的意识,接下来咱们整合目前的支流框架springBoot,实现权限的治理。

在这之前,假设你曾经理解了基于资源的权限治理模型。数据库设计的表有 user 、role、user_role、permission、role_permission。

步骤:

默认大家都曾经数据库曾经好,曾经有了下面提到的表。(文末提供sql脚本下载)

第一步:在pom.xml文件中引入相干jar包

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>pers.lbf</groupId>
    <artifactId>springboot-spring-securioty-demo1</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-spring-security-demo1</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

第二步:批改application.yml文件,增加数据库相干配置

server:
  port: 8081
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/secutiry_authority?useSSL=false&serverTimezone=GMT
    username: root
    password: root1997
    driver-class-name: com.mysql.cj.jdbc.Driver

第三步:启动我的项目

springboot曾经给咱们提供好了一个默认的username为“user”,其明码能够在控制台输入中失去。并且在springBoot的默认配置中,所有资源必须要通过认证后能力拜访

关上<http://127.0.0.1:8081/login 即可看到默认的登录页面。

第四步:增加配置类,笼罩springBoot对spring security的默认配置

/**
 * @author 赖柄沣 bingfengdev@aliyun.com
 * @version 1.0
 * @date 2020/8/28 20:22
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled  = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userService;


    @Autowired
    private BCryptPasswordEncoder passwordEncoder;


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder);
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {

        //禁用跨域爱护
        http.csrf().disable();

        //配置自定义登录页
        http.formLogin()
                .loginPage("/login.html")
                .loginProcessingUrl("/login")
                .defaultSuccessUrl("/")
                .usernameParameter("username")
                .passwordParameter("password");

        //配置登出
        http.logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login.html");


    }

    @Override
    public void configure(WebSecurity webSecurity) throws Exception{
        //疏忽动态资源
        webSecurity.ignoring().antMatchers("/assents/**","/login.html");
    }



}

对于EnableGlobalMethodSecurity注解的阐明

@EnableGlobalMethodSecurity(securedEnabled=true)
开启@Secured 注解过滤权限

@EnableGlobalMethodSecurity(jsr250Enabled=true)
开启@RolesAllowed 注解过滤权限

@EnableGlobalMethodSecurity(prePostEnabled=true)
应用表达式工夫办法级别的安全性 4个注解可用

 @PreAuthorize 在办法调用之前,基于表达式的计算结果来限度对办法的拜访
 @PostAuthorize 容许办法调用,然而如果表达式计算结果为false,将抛出一个安全性异样
 @PostFilter 容许办法调用,但必须依照表达式来过滤办法的后果
 @PreFilter 容许办法调用,但必须在进入办法之前过滤输出值

第五步:编写代码,实现对User、role、permission的CRUD

5.1 编写本人的user对象,实现spring security的UserDetails接口,并实现对User的查找操作

对于为什么要实现这个接口,大家能够参考我上一篇文章《Spring Security认证流程剖析–练气前期》。

/**
 * @author 赖柄沣 bingfengdev@aliyun.com
 * @version 1.0
 * @date 2020/8/28 22:14
 */
public class UserDO implements UserDetails {

    private Integer id;
    private String username;
    private String password;
    private Integer status;
    private List<SimpleGrantedAuthority> authorityList;


    @Override
    public String toString() {
        return "UserDO{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", status=" + status +
                ", authorityList=" + authorityList +
                '}';
    }

    public List<SimpleGrantedAuthority> getAuthorityList() {
        return authorityList;
    }

    public void setAuthorityList(List<SimpleGrantedAuthority> authorityList) {
        this.authorityList = authorityList;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    /**
     * Returns the authorities granted to the user. Cannot return <code>null</code>.
     *
     * @return the authorities, sorted by natural key (never <code>null</code>)
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {

        return this.authorityList;
    }

    /**
     * Returns the password used to authenticate the user.
     *
     * @return the password
     */
    @Override
    public String getPassword() {
        return this.password;
    }

    /**
     * Returns the username used to authenticate the user. Cannot return <code>null</code>.
     *
     * @return the username (never <code>null</code>)
     */
    @Override
    public String getUsername() {
        return this.username;
    }

    /**
     * Indicates whether the user's account has expired. An expired account cannot be
     * authenticated.
     *
     * @return <code>true</code> if the user's account is valid (ie non-expired),
     * <code>false</code> if no longer valid (ie expired)
     */
    @Override
    public boolean isAccountNonExpired() {
        return this.status==1;
    }

    /**
     * Indicates whether the user is locked or unlocked. A locked user cannot be
     * authenticated.
     *
     * @return <code>true</code> if the user is not locked, <code>false</code> otherwise
     */
    @Override
    public boolean isAccountNonLocked() {
        return this.status == 1;
    }

    /**
     * Indicates whether the user's credentials (password) has expired. Expired
     * credentials prevent authentication.
     *
     * @return <code>true</code> if the user's credentials are valid (ie non-expired),
     * <code>false</code> if no longer valid (ie expired)
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * Indicates whether the user is enabled or disabled. A disabled user cannot be
     * authenticated.
     *
     * @return <code>true</code> if the user is enabled, <code>false</code> otherwise
     */
    @Override
    public boolean isEnabled() {
        return this.status==1;
    }
}

对于用户凭证是否过期、账户是否被锁定大家能够本人实现一下

**
 * @author 赖柄沣 bingfengdev@aliyun.com
 * @version 1.0
 * @date 2020/8/28 22:17
 */
public interface IUserDao {

    @Select("select * from sys_user u where u.username=#{name}")
    UserDO findByName(String name);
}

5.2 编写Role和Permission两个实体类,并实现对其查找的Dao

RoleDO

/**
 * @author 赖柄沣 bingfengdev@aliyun.com
 * @version 1.0
 * @date 2020/9/1 20:51
 */
public class RoleDO implements Serializable {
    private Integer id;
    private String roleName;
    private String roleDesc;
}

PermissionDO

/**
 * @author 赖柄沣 bingfengdev@aliyun.com
 * @version 1.0
 * @date 2020/9/1 21:27
 */
public class PermissionDO implements Serializable {
    private Integer id;
    private String permissionName;
    private String permissionUrl;
    private Integer parentId;

   
}

IRoleDao

/**
 * @author 赖柄沣 bingfengdev@aliyun.com
 * @version 1.0
 * @date 2020/9/1 20:53
 */
public interface IRoleDao {

    @Select("select * from sys_role sr where sr.id in (select rid from sys_user_role where uid=#{userId})")
    List<RoleDO> findByUserId(Integer userId);
}

IPermissionDao

/**
 * @author 赖柄沣 bingfengdev@aliyun.com
 * @version 1.0
 * @date 2020/9/1 21:30
 */
public interface IPermissonDao {

    @Select("select * from sys_permission sp where sp.id in (select pid from sys_role_permission where rid=#{roleId})")
    List<PermissionDO> findByRoleId(Integer roleId);
}

5.3 编写UserService 实现UserDetailsService接口并实现loadUserByUsername办法

**
 * @author 赖柄沣 bingfengdev@aliyun.com
 * @version 1.0
 * @date 2020/8/28 22:16
 */
@Service("userService")
public class UserServiceImpl implements UserDetailsService {

    @Autowired
    private IUserDao userDao;
    @Autowired
    private IRoleDao roleDao;
    @Autowired
    private IPermissonDao permissonDao;


    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if (username == null){
            return null;
        }
       
        UserDO user = userDao.findByName(username);
//加载权限
        List<RoleDO> roleList = roleDao.findByUserId(user.getId());

        List<SimpleGrantedAuthority> list  = new ArrayList<> ();
        for (RoleDO roleDO : roleList) {
            List<PermissionDO> permissionListItems = permissonDao.findByRoleId(roleDO.getId());
            for (PermissionDO permissionDO : permissionListItems) {
                list.add(new SimpleGrantedAuthority(permissionDO.getPermissionUrl()));
            }
        }
        user.setAuthorityList(list);
       
        return user;
    }
}

第六步:编写一个测试接口

/**
 * @author 赖柄沣 bingfengdev@aliyun.com
 * @version 1.0
 * @date 2020/8/27 20:02
 */
@RestController
@RequestMapping("/product")
public class TestController {


    @GetMapping("/")
    @PreAuthorize("hasAuthority('product:get')")
    public String get() {
        return "调用胜利";
    }
}

第七步 :应用postman进行测试

7.1登录操作

登录成绩返回主页

登录失败返回登录页面

7.2调用受爱护的接口

有权限则调用胜利

无权限返回403

大家能够实现一下对异样的拦挡,给用户返回一个敌对的提醒。

写在最初

这是springBoot整合spring security单体利用的一个小demo。对于分布式的、应用JWT代替spring security 的csrf,并自定义认证器的例子将在我的下一篇文章中介绍。

代码及sql脚本下载:https://github.com/code81192/…

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理