zoukankan      html  css  js  c++  java
  • Spring Boot 整合 Spring Security 示例实现前后分离权限注解 + JWT 登录认证

    Spring Boot 整合 Spring Security 示例实现前后分离权限注解 + JWT 登录认证

    1:建表:用户、角色、资源

    CREATE TABLE `user` (
      `id` int(64) NOT NULL AUTO_INCREMENT,
      `user_name` varchar(32) DEFAULT NULL,
      `password` varchar(255) DEFAULT NULL,
      `enable` tinyint(4) DEFAULT '0',
      `locked` tinyint(4) DEFAULT '0',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
    
    
    CREATE TABLE `role` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `name` varchar(32) DEFAULT NULL,
      `description` varchar(255) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
    
    
    CREATE TABLE `user_role` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `user_id` int(11) DEFAULT NULL,
      `role_id` int(11) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
    
    
    CREATE TABLE `role_resource` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `role_id` int(11) DEFAULT NULL,
      `resource_id` int(11) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8
    
    
    CREATE TABLE `resources` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `pattern` varchar(255) DEFAULT NULL,
      `name` varchar(255) DEFAULT NULL COMMENT '资源名字',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
    
    
    INSERT INTO `resources` VALUES (1, 'getUserInfo','查看用户信息');
    INSERT INTO `resources` VALUES (2, 'getResurcesInfo', '查看所有权限');
    INSERT INTO `resources` VALUES (3, 'getRoleInfo', '查看所有角色');
    
    
    INSERT INTO `role` VALUES (1, 'ADMIN','管理员');
    INSERT INTO `role` VALUES (2, 'USER', '普通用户');
    
    
    INSERT INTO `role_resource` VALUES (1, 1, 1);
    INSERT INTO `role_resource` VALUES (2, 1, 2);
    INSERT INTO `role_resource` VALUES (3, 1, 3);
    INSERT INTO `role_resource` VALUES (4, 2, 1);
    
    
    INSERT INTO `user`(id, user_name, `password`) VALUES (1, 'admin', '123456');
    INSERT INTO `user`(id, user_name, `password`) VALUES (2, 'user', '123456');
    
    
    INSERT INTO `user_role` VALUES (1, 1, 1);
    INSERT INTO `user_role` VALUES (2, 2, 2);

    2、Maven依赖如下:

     <!-- StringUtilS工具 -->
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
                <version>3.5</version>
            </dependency>
            <!-- JSON工具 -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.45</version>
            </dependency>
            <!-- JWT依赖 -->
            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-jwt</artifactId>
                <version>1.0.9.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt</artifactId>
                <version>0.9.0</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
            <!--Security依赖 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
            <!-- Druid 数据库连接池 -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>1.1.22</version>
            </dependency>
            <!-- region MyBatis -->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>1.3.1</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>runtime</scope>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-configuration-processor</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </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>

    3、yml配置如下:

    server:
      port: 8848
    
    mybatis:
      mapper-locations: classpath*:mapper/*Mapper.xml
      configuration:
          database-id: MySQL
         # 开启驼峰转换
          map-underscore-to-camel-case: true
          # spring boot集成mybatis的方式打印sql
    #      log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    
    spring:
      application:
        name: security-jwt-study
      datasource:
        druid:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://127.0.0.1:3306/my-study?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false
          username: root
          password: 123456
          filters: stat
          # 设置最大数据库连接数,设为0为无限制
          maxActive: 20
          # 配置初始化大小、最小、最大
          initialSize: 1
          #  最大等待时间
          maxWait: 60000
          # 始终保留在池中的最小连接数,如果连接验证失败将缩小至此值
          minIdle: 1
          timeBetweenEvictionRunsMillis: 6000
    #      连接在池中保持空闲而不被回收的最小时间(毫秒)
          minEvictableIdleTimeMillis: 30000
          validationQuery: select 'x'
    #      对池中空闲的连接是否进行验证,验证失败则回收此连接(默认为false)
          testWhileIdle:  true
    #       当从连接池中取出一个连接时是否进行验证,若验证失败则从池中删除该连接并尝试取出另一个连接
          testOnBorrow: true
    #      当一个连接使用完归还到连接池时是否进行验证
          testOnReturn: false
    #      启用游标缓存,这个对数据库的性能提升很大
          poolPreparedStatements: true
    #        要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
          maxOpenPreparedStatements: 20
          filter:
            stat:
              log-slow-sql: true
              slow-sql-millis: 2000

    4、编写项目基础类

    Entity,Dao,Service,及等SpringSecurity用户的Entity,Service类等在这里省略,请参考源码

    编写JWT工具类

    package com.dw.study.jwt;
    
    import com.alibaba.fastjson.JSON;
    import com.dw.study.entity.MyUser;
    import io.jsonwebtoken.*;
    import lombok.extern.slf4j.Slf4j;
    
    import javax.crypto.SecretKey;
    import java.util.Date;
    
    /**
     * JWT工具类
     *
     * @Author Sans
     * @CreateTime 2019/10/2 7:42
     */
    @Slf4j
    public class JWTTokenUtil {
        // JWT的过期时间
        public static final long JWT_EXPIRE = 60 * 60 * 24*1000;  //过期时间,毫秒,一天
        //   JWT的生成密钥
        public static final String JWT_SECURITY = "JWTSecret";
        // #请求的 Header KEY
        public static final String HEAD_KEY = "Authorization";
        // 生成的Token的前缀:
        public static final String TOKEN_PREFIX = "Sans-";
    
    
        /**
         * 生成Token
         *
         * @Author Sans
         * @CreateTime 2019/10/2 12:16
         * @Param selfUserEntity 用户安全实体
         * @Return Token
         */
        public String createAccessToken(MyUser selfUserEntity) {
            // 登陆成功生成JWT
            String token = Jwts.builder()
                    //设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
                    .setId(selfUserEntity.getId() + "")
                    // 主题(代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。)
                    .setSubject(selfUserEntity.getUsername())
                    // 签发时间
                    .setIssuedAt(new Date())
                    // 签发者
                    .setIssuer("sans")
                    // 自定义属性 放入用户拥有权限
                    .claim("authorities", JSON.toJSONString(selfUserEntity.getAuthorities()))
                    // 失效时间
                    .setExpiration(new Date(System.currentTimeMillis() + JWT_EXPIRE))
                    // 设置签名使用的签名算法和签名使用的秘钥
                    //生成签名的时候使用的秘钥secret,这个方法在本地封装,一般可以从本地配置文件中读取,切记这个秘钥不能外露哦。
                    // 它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
                    .signWith(SignatureAlgorithm.HS512, JWT_SECURITY)
                    //就开始压缩为xxxxxxxxxxxxxx.xxxxxxxxxxxxxxx.xxxxxxxxxxxxx这样的jwt
                    .compact();
            return token;
        }
    
        /**
         * @return
         * @Author dw
         * @Description 解析JWT-Token
         * @Date 2020/5/14 16:31
         * @Param
         */
        public static Claims parseJWT(String jwt) throws Exception {
            Claims claims = Jwts.parser()  //得到DefaultJwtParser
                    //设置签名的秘钥
                    .setSigningKey(JWT_SECURITY)
                    //设置需要解析的jwt
                    .parseClaimsJws(jwt).getBody();
            return claims;
        }
    
        /**
         * 获取token的过期时间
         *
         * @param token token
         * @return 过期时间
         */
        public Date getExpirationDateFromToken(String token) throws Exception {
            return parseJWT(token)
                    .getExpiration();
        }
    
        /**
         * 判断token是否过期
         *
         * @param token token
         * @return 已过期返回true,未过期返回false
         */
        private Boolean isTokenExpired(String token) throws Exception {
            Date expiration = getExpirationDateFromToken(token);
            return expiration.before(new Date());
        }
    
    
    }

    编写暂无权限处理类

    /**
     * @Description 没有权限时 的处理类
     * @Author Sans
     * @CreateTime 2019/10/3 8:39
     */
    @Component
    public class UserAuthAccessDeniedHandler implements AccessDeniedHandler {
        /**
         * 暂无权限返回结果
         * @Author Sans
         * @CreateTime 2019/10/3 8:41
         */
        @Override
        public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception){
            ResultUtil.responseJson(response,ResultUtil.resultCode(403,"暂无权限访问,请管理员授权"));
        }
    }

    编写用户未登录处理类

    /**
     * 用户未登录处理类
     * @Author Sans
     * @CreateTime 2019/10/3 8:55
     */
    @Component
    public class UserAuthenticationEntryPointHandler implements AuthenticationEntryPoint {
        /**
         * 用户未登录返回结果
         * @Author Sans
         * @CreateTime 2019/10/3 9:01
         */
        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception){
            ResultUtil.responseJson(response,ResultUtil.resultCode(401,"未登录"));
        }
    }

    编写登录失败处理类

    @Slf4j
    @Component
    public class UserLoginFailureHandler implements AuthenticationFailureHandler {
        /**
         * 登录失败返回结果
         * @Author Sans
         * @CreateTime 2019/10/3 9:12
         */
        @Override
        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception){
            // 这些对于操作的处理类可以根据不同异常进行不同处理
            if (exception instanceof UsernameNotFoundException){
                log.info("【登录失败】"+exception.getMessage());
                ResultUtil.responseJson(response,ResultUtil.resultCode(500,"用户名不存在"));
            }else if (exception instanceof LockedException){
                log.info("【登录失败】"+exception.getMessage());
                ResultUtil.responseJson(response,ResultUtil.resultCode(500,"用户被冻结"));
            } else if (exception instanceof BadCredentialsException){
                log.info("【登录失败】"+exception.getMessage());
                ResultUtil.responseJson(response,ResultUtil.resultCode(500,"用户名密码不正确"));
            } else {
                ResultUtil.responseJson(response,ResultUtil.resultCode(500,"登录失败,用户账号异常"));
            }
        }
    }

    编写登录成功处理类

    @Slf4j
    @Component
    public class UserLoginSuccessHandler implements AuthenticationSuccessHandler {
    
        /**
         * 登录成功返回结果
         * @Author Sans
         * @CreateTime 2019/10/3 9:27
         */
        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication){
            // 组装JWT
            MyUser selfUserEntity =  (MyUser) authentication.getPrincipal();
            JWTTokenUtil jwtTokenUtil = new JWTTokenUtil();
            String token = jwtTokenUtil.createAccessToken(selfUserEntity);
            token = JWTTokenUtil.TOKEN_PREFIX + token;
            // 封装返回参数
            Map<String,Object> resultData = new HashMap<>();
            resultData.put("code","200");
            resultData.put("msg", "登录成功");
            resultData.put("token",token);
            ResultUtil.responseJson(response,resultData);
        }
    
    }

    编写登出成功处理类

    /**
     * 登出成功处理类
     * @Author Sans
     * @CreateTime 2019/10/3 9:42
     */
    @Component
    public class UserLogoutSuccessHandler implements LogoutSuccessHandler {
        /**
         * 用户登出返回结果
         * 这里应该让前端清除掉Token
         * @Author Sans
         * @CreateTime 2019/10/3 9:50
         */
        @Override
        public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication){
            Map<String,Object> resultData = new HashMap<>();
            resultData.put("code","200");
            resultData.put("msg", "登出成功");
            SecurityContextHolder.clearContext();
            ResultUtil.responseJson(response,ResultUtil.resultSuccess(resultData));
        }
    
    }

    五、编写Security核心类

    编写自定义登录验证类

    /**
     * 自定义登录验证
     * @Author Sans
     * @CreateTime 2019/10/1 19:11
     */
    @Component
    public class UserAuthenticationProvider implements AuthenticationProvider {
        @Autowired
        private UserMapperDao userMapperDao;
    
        @Autowired
        private BCryptPasswordEncoder bCryptPasswordEncoder;
    
        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            // 获取表单输入中返回的用户名
            String userName = (String) authentication.getPrincipal();
            // 获取表单中输入的密码
            String password = (String) authentication.getCredentials();
            // 查询用户是否存在
            MyUser userDetails = userMapperDao.loadUserByUsername(userName);
            if (userDetails == null) {
                throw new UsernameNotFoundException("用户名不存在");
            }
            // 我们还要判断密码是否正确,这里我们的密码使用BCryptPasswordEncoder进行加密的
            if (!bCryptPasswordEncoder.matches(password, bCryptPasswordEncoder.encode(userDetails.getPassword()))) {
                throw new BadCredentialsException("密码不正确");
            }
            // 还可以加一些其他信息的判断,比如用户账号已停用等判断
            if (!userDetails.isAccountNonLocked()){
                throw new LockedException("该用户已被冻结");
            }
            // 查询用户角色
            List<Role> userRoles = userMapperDao.getUserRolesByUid(userDetails.getId());
            // ROLE_ 是springsecurity对于角色的默认前缀,如果不加,验证会失败,数据库需要添加前缀
            // 角色集合
            HashSet<GrantedAuthority> authorities = new HashSet<>();
            // ROLE_ 是springsecurity对于角色的默认前缀,如果不加,验证会失败(或者在数据库中,添加Role时添加前缀)
            for (Role role: userRoles){
                authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()));
            }
            userDetails.setAuthorities(authorities);
            // 进行登录
            return new UsernamePasswordAuthenticationToken(userDetails, password);
        }
        @Override
        public boolean supports(Class<?> authentication) {
            return true;
        }
    }

    编写自定义PermissionEvaluator注解验证

    **
     * 自定义权限注解验证
     * @Author Sans
     * @CreateTime 2019/10/6 13:31
     */
    @Component
    public class UserPermissionEvaluator implements PermissionEvaluator {
        @Autowired
        private ResourceMapperDao resourceMapperDao;
        /**
         * hasPermission鉴权方法
         * 这里仅仅判断PreAuthorize注解中的权限表达式
         * 实际中可以根据业务需求设计数据库通过targetUrl和permission做更复杂鉴权
         * 当然targetUrl不一定是URL可以是数据Id还可以是管理员标识等,这里根据需求自行设计
         * @Author Sans
         * @CreateTime 2019/10/6 18:25
         * @Param  authentication  用户身份(在使用hasPermission表达式时Authentication参数默认会自动带上)
         * @Param  targetUrl  请求路径
         * @Param  permission 请求路径权限
         * @Return boolean 是否通过
         */
        @Override
        public boolean hasPermission(Authentication authentication, Object targetUrl, Object permission) {
            // 获取用户信息
            MyUser selfUserEntity =(MyUser) authentication.getPrincipal();
            // 查询用户权限(这里可以将权限放入缓存中提升效率)
            Set<String> permissions = new HashSet<>();
            List<Resources> userResources = resourceMapperDao.queryResourcesByUserId(selfUserEntity.getId());
            for (Resources resources: userResources) {
                permissions.add(resources.getPattern());
            }
            // 权限对比
            if (permissions.contains(permission.toString())){
                return true;
            }
            return false;
        }
        @Override
        public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
            return false;
        }
    }

    编写SpringSecurity核心配置类

    /**
     * SpringSecurity配置类
     * @Author Sans
     * @CreateTime 2019/10/1 9:40
     */
    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true) //开启权限注解,默认是关闭的
    public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
    
        /**
         * 自定义登录成功处理器
         */
        @Autowired
        private UserLoginSuccessHandler userLoginSuccessHandler;
        /**
         * 自定义登录失败处理器
         */
        @Autowired
        private UserLoginFailureHandler userLoginFailureHandler;
        /**
         * 自定义注销成功处理器
         */
        @Autowired
        private UserLogoutSuccessHandler userLogoutSuccessHandler;
        /**
         * 自定义暂无权限处理器
         */
        @Autowired
        private UserAuthAccessDeniedHandler userAuthAccessDeniedHandler;
        /**
         * 自定义未登录的处理器
         */
        @Autowired
        private UserAuthenticationEntryPointHandler userAuthenticationEntryPointHandler;
        /**
         * 自定义登录逻辑验证器
         */
        @Autowired
        private UserAuthenticationProvider userAuthenticationProvider;
    
        /**
         * @Author dw
         * @Description 如果不使用上面的自定义登录逻辑,就走默认的认证
         * @Date 2020/5/14 17:46
         * @Param
         * @return
         */
      //  @Autowired
     //   public MyUserDetailServiceImpl myUserDetailService;
    
        /**
         * 加密方式
         * @Author Sans
         * @CreateTime 2019/10/1 14:00
         */
        @Bean
        public BCryptPasswordEncoder bCryptPasswordEncoder(){
            return new BCryptPasswordEncoder();
        }
        /**
         * 注入自定义PermissionEvaluator
         */
        @Bean
        public DefaultWebSecurityExpressionHandler userSecurityExpressionHandler(){
            DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
            handler.setPermissionEvaluator(new UserPermissionEvaluator());
            return handler;
        }
    
        /**
         * 配置登录验证逻辑
         */
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            // security 默认的认证逻辑
    //        auth.userDetailsService(myUserDetailService);
            //这里可启用我们自己的登陆验证逻辑
            auth.authenticationProvider(userAuthenticationProvider);
        }
        /**
         * 配置security的控制逻辑
         * @Author Sans
         * @CreateTime 2019/10/1 16:56
         * @Param  http 请求
         */
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    // 不进行权限验证的请求或资源
                   .antMatchers("/login/*","/index").permitAll()
                    // 其他的需要登陆后才能访问
                    .anyRequest().authenticated()
                    .and()
                    // 配置未登录自定义处理类
                    .httpBasic().authenticationEntryPoint(userAuthenticationEntryPointHandler)
                    .and()
                    // 配置登录地址
                    .formLogin()
                    .loginProcessingUrl("/login/userLogin")
                    // 配置登录成功自定义处理类
                    .successHandler(userLoginSuccessHandler)
                    // 配置登录失败自定义处理类
                    .failureHandler(userLoginFailureHandler)
                    .and()
                    // 配置登出地址
                    .logout()
                    .logoutUrl("/login/userLogout")
                    // 配置用户登出自定义处理类
                    .logoutSuccessHandler(userLogoutSuccessHandler)
                    .and()
                    // 配置没有权限自定义处理类
                    .exceptionHandling().accessDeniedHandler(userAuthAccessDeniedHandler)
                    .and()
                    // 开启跨域
                    .cors()
                    .and()
                    // 取消跨站请求伪造防护
                    .csrf().disable();
            // 基于Token不需要session
            http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
            // 禁用缓存
            http.headers().cacheControl();
            // 添加JWT过滤器
            http.addFilter(new JWTAuthenticationTokenFilter(authenticationManager()));
        }
    
    }

    6、编写JWT拦截类

    编写JWT接口请求校验拦截器

    /**
     * JWT接口请求校验拦截器
     * 请求接口时会进入这里验证Token是否合法和过期,如果令牌合法则获取用户信息,并且存入SecurityContextHolder
     * @Author Sans
     * @CreateTime 2019/10/5 16:41
     */
    @Slf4j
    public class JWTAuthenticationTokenFilter extends BasicAuthenticationFilter {
    
    
        public JWTAuthenticationTokenFilter(AuthenticationManager authenticationManager) {
            super(authenticationManager);
        }
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            // 获取请求头中JWT的Token
            String tokenHeader = request.getHeader(JWTTokenUtil.HEAD_KEY);
            if (null!=tokenHeader && tokenHeader.startsWith(JWTTokenUtil.TOKEN_PREFIX)) {
                try {
                    // 截取JWT前缀
                    String token = tokenHeader.replace(JWTTokenUtil.TOKEN_PREFIX, "");
                    // 解析JWT
                    Claims claims = JWTTokenUtil.parseJWT(token);
                    // 获取用户名
                    String username = claims.getSubject();
                    String userId=claims.getId();
    
                    if(StringUtils.isNotEmpty(username) && StringUtils.isNotEmpty(userId)) {
                        // 获取角色
                        List<GrantedAuthority> authorities = new ArrayList<>();
                        String authority = claims.get("authorities").toString();
                        if(!StringUtils.isEmpty(authority)){
                            List<Map<String,String>> authorityMap = JSONObject.parseObject(authority, List.class);
                            for(Map<String,String> role : authorityMap){
                                if(null != role) {
                                    authorities.add(new SimpleGrantedAuthority(role.get("authority")));
                                }
                            }
                        }
                        //组装参数
                        MyUser selfUserEntity = new MyUser();
                        selfUserEntity.setUserName(claims.getSubject());
                        selfUserEntity.setId(claims.getId());
                        selfUserEntity.setAuthorities(authorities);
                        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(selfUserEntity, userId, authorities);
                        SecurityContextHolder.getContext().setAuthentication(authentication);
                    }
                } catch (ExpiredJwtException e){
                    log.info("Token过期");
                } catch (Exception e) {
                    log.info("Token无效");
                }
            }
            filterChain.doFilter(request, response);
        }
    
    }

    7、权限注解和hasPermission权限扩展

            Security允许我们在定义URL方法访问所应有的注解权限时使用SpringEL表达式,在定义所需的访问权限时如果对应的表达式返回结果为true则表示拥有对应的权限,反之则没有权限,会进入到我们配置的UserAuthAccessDeniedHandler(暂无权限处理类)中进行处理

    eg: 

    /**
         * 管理端信息
         * @Author Sans
         * @CreateTime 2019/10/2 14:22
         * @Return Map<String,Object> 返回数据MAP
         */
        @PreAuthorize("hasRole('ADMIN')")
        @RequestMapping(value = "/info",method = RequestMethod.GET)
        public Map<String,Object> userLogin(){
            Map<String,Object> result = new HashMap<>();
            SelfUserEntity userDetails = SecurityUtil.getUserInfo();
            result.put("title","管理端信息");
            result.put("data",userDetails);
            return ResultUtil.resultSuccess(result);
        }
        /**
         * 拥有ADMIN或者USER角色可以访问
         * @Author Sans
         * @CreateTime 2019/10/2 14:22
         * @Return Map<String,Object> 返回数据MAP
         */
        @PreAuthorize("hasAnyRole('ADMIN','USER')")
        @RequestMapping(value = "/list",method = RequestMethod.GET)
        public Map<String,Object> list(){
            Map<String,Object> result = new HashMap<>();
            List<SysUserEntity> sysUserEntityList = sysUserService.list();
            result.put("title","拥有用户或者管理员角色都可以查看");
            result.put("data",sysUserEntityList);
            return ResultUtil.resultSuccess(result);
        }
        /**
         * 拥有ADMIN和USER角色可以访问
         * @Author Sans
         * @CreateTime 2019/10/2 14:22
         * @Return Map<String,Object> 返回数据MAP
         */
        @PreAuthorize("hasRole('ADMIN') and hasRole('USER')")
        @RequestMapping(value = "/menuList",method = RequestMethod.GET)
        public Map<String,Object> menuList(){
            Map<String,Object> result = new HashMap<>();
            List<SysMenuEntity> sysMenuEntityList = sysMenuService.list();
            result.put("title","拥有用户和管理员角色都可以查看");
            result.put("data",sysMenuEntityList);
            return ResultUtil.resultSuccess(result);
        }

             通常情况下使用hasRole和hasAnyRole基本可以满足大部分鉴权需求,但是有时候面对更复杂的场景上述常规表示式无法完成权限认证,Security也为我们提供了解决方案.通过hasPermission()来扩展表达式.使用hasPermission()首先要实现PermissionEvaluator接口(参考上面的自定义权限验证)

    在请求方法上添加hasPermission示例:

    /**
         * 拥有sys:user:info权限可以访问
         * hasPermission 第一个参数是请求路径 第二个参数是权限表达式
         * @Author Sans
         * @CreateTime 2019/10/2 14:22
         * @Return Map<String,Object> 返回数据MAP
         */
        @PreAuthorize("hasPermission('/admin/userList','sys:user:info')")
        @RequestMapping(value = "/userList",method = RequestMethod.GET)
        public Map<String,Object> userList(){
            Map<String,Object> result = new HashMap<>();
            List<SysUserEntity> sysUserEntityList = sysUserService.list();
            result.put("title","拥有sys:user:info权限都可以查看");
            result.put("data",sysUserEntityList);
            return ResultUtil.resultSuccess(result);
        }

    hasPermission可以也可以和其他表达式联合使用

    /**
         * 拥有ADMIN角色和sys:role:info权限可以访问
         * @Author Sans
         * @CreateTime 2019/10/2 14:22
         * @Return Map<String,Object> 返回数据MAP
         */
        @PreAuthorize("hasRole('ADMIN') and hasPermission('/admin/adminRoleList','sys:role:info')")
        @RequestMapping(value = "/adminRoleList",method = RequestMethod.GET)
        public Map<String,Object> adminRoleList(){
            Map<String,Object> result = new HashMap<>();
            List<SysRoleEntity> sysRoleEntityList = sysRoleService.list();
            result.put("title","拥有ADMIN角色和sys:role:info权限可以访问");
            result.put("data",sysRoleEntityList);
            return ResultUtil.resultSuccess(result);
        }

    8、测试

    登录USER角色账号,登录成功后我们会获取到身份认证的Token

     访问USER角色的接口,把上一步获取到的Token设置在Headers中,Key为Authorization,我们之前实现的JWTAuthenticationTokenFilter拦截器会根据请求头中的Authorization获取并解析Token

    好的,结束啦!!!

    github源码地址:

    https://github.com/dengwei-java/spring-security-Jwt-oauth2-study/tree/master/spring-security-jwt

  • 相关阅读:
    Vuex 在state中存取数据 modules分模块 (2018/11/28)
    计数器(2018/11/29)
    03$router和$route的区别 (2018/11/28)
    02导航守卫 (2018/11/28)
    01模拟用户的登录 (2018/11/27)
    路由传参(2018/11/26)
    CSS制作红桃心
    css制作三角形
    css内容超出显示省略号
    css制作旋转风车(transform 篇)
  • 原文地址:https://www.cnblogs.com/dw3306/p/12893457.html
Copyright © 2011-2022 走看看