zoukankan      html  css  js  c++  java
  • 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/art-demo/tree/master/springboot-spring-securioty-demo1

  • 相关阅读:
    canvas 平移&缩放
    html + css + jquery实现简单的进度条实例
    jQuery Layer mobile 弹出层
    jQuery-全屏滚动插件【fullPage.js】API 使用方法总结
    Hive:select count(distinct)优化以及hive.groupby.skewindata
    Spark Application、Driver、Job、stage、task
    Spark 参数说明
    Xgboost小结与调参
    梯度下降算法过程详细解读
    机器学习杂记
  • 原文地址:https://www.cnblogs.com/bingfengdev/p/13599237.html
Copyright © 2011-2022 走看看