zoukankan      html  css  js  c++  java
  • Shiro-JWT SpringBoot前后端分离权限认证的一种思路

    JWT-Shiro 整合

    JWT-与Shiro整合进行授权认证的大致思路 图示

    大致思路

    1. 将登录验证从shiro中分离,自己结合JWT实现
    2. 用户登陆后请求认证服务器进行密码等身份信息确认,确认成功后 封装相关用户信息 生成token 相应给前端.
    3. 之后每次访问资源接口都在请求头中携带认证时生成的token
    4. 当发起资源请求时首先请求被请求过滤器拦截,拦截后判断请求头中是否含有token
    5. 如果含有token对token进行认证认证成功后对token进行解析,之后进行授权,拥有权限则进行放行
    6. 反之返回相关错误信息

    核心点

    • token相关工具类的封装
    • 自定义重写shiro过滤器 extends AccessControlFilter
    • 自定义实现shiro Realm extends AuthorizingRealm
    • 实现自定义的shiroToken implements AuthenticationToken

    具体代码

    生成token的工具类

    public class JWTUtils {
        //密钥用于生成token的签名
        private static final String SIGN = "!1qaz.(";
    
        /**
         * 生成token
         */
        public static String getToken(String userId,String userName, String roles, String permissions) {
            Calendar instance = Calendar.getInstance();
            instance.add(Calendar.DATE, 7);
            JWTCreator.Builder builder = JWT.create()
                    .withIssuer("HuangShen")//token签发者
                    .withExpiresAt(instance.getTime()) //过期时间
                    .withClaim("userId", userId)//相关信息
                    .withClaim("userName", userName)
                    .withClaim("roles", roles)
                    .withClaim("permissions", permissions);
    
            //使用HMAC256算法生成token
            String token = builder.sign(Algorithm.HMAC256(SIGN)); 
            return token;
        }
    
        /**
         * 解码token
         *
         * @param token token 
         */
        public static DecodedJWT verify(String token){
            DecodedJWT verify =JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
            return verify;
        }
    

    自定义重写shiro过滤器

    public class JWTFilter extends AccessControlFilter {
    
        /**
         * 此方法首先执行当此方法返回false时继续执行onAccessDenied方法
         * 返回true允许访问
         * 返回 false拒绝访问
         */
        @Override
        protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
               
            //获取主体对象
            Subject subject = SecurityUtils.getSubject();
            System.out.println("===允许访问===");
            //当主体对象不为空且已经获得认证时允许访问 
            if (null != subject && subject.isAuthenticated()){
                return true;
            }
                return false;
        }
    
    
        /**
         * 当isAccessAllowed返回值为false时执行
         */
        @Override
        protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
            HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
            String token = httpServletRequest.getHeader("token");
            //客户端没有携带token
            if (StringUtils.isEmpty(token)) {
                System.out.println("请求头没有token");
                return true;
            }
            System.out.println("拒接访问");
            JWTToken jwtToken = new JWTToken(token);
            Subject subject = SecurityUtils.getSubject();
            //进行认证
            subject.login(jwtToken);
            return true;
        }
    

    自定义实现shiro Realm

    /**
     * 继承AuthorizingRealm类重写doGetAuthorizationInfo(授权)
     * doGetAuthenticationInfo(认证)
     */
    public class CustomerRealm extends AuthorizingRealm {
    
        /**
         * 认证
         * @param authenticationToken  认证token
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            //获取主体信息
            JWTToken principal = (JWTToken) authenticationToken;
            DecodedJWT verify;
            //创建自定义principal并赋值
            TokenPayload tokenPayload = new TokenPayload();
            //解析token
            try {
                verify = JWTUtils.verify((String) principal.getPrincipal());
                tokenPayload.setUserId(verify.getClaim("userId").asString());
                tokenPayload.setRoles(verify.getClaim("roles").asString());
                tokenPayload.setUserName(verify.getClaim("userName").asString());
                tokenPayload.setPermissions(verify.getClaim("permissions").asString());
            } catch (AlgorithmMismatchException exception) {
                throw new AuthenticationException("算法不匹配异常" + exception.getMessage());
            } catch (SignatureVerificationException exception) {
                throw new AuthenticationException("签名验证异常" + exception.getMessage());
            } catch (TokenExpiredException exception) {
                throw new AuthenticationException("token过期异常" + exception.getMessage());
            } catch (InvalidClaimException exception) {
                throw new AuthenticationException("无效Claim异常" + exception.getMessage());
            } catch (JWTDecodeException exception) {
                throw new AuthenticationException("JWT解码异常" + exception.getMessage());
            } catch (JWTVerificationException exception) {
                throw new AuthenticationException("JWT验证异常" + exception.getMessage());
            } catch (RuntimeException exception) {
                throw new RuntimeException(exception.getMessage());
            }
    
    
            System.out.println("认证完成");
            //将token解析过后的信息封装成为主体传入 授权时使用
            return new SimpleAuthenticationInfo(tokenPayload, true, this.getName());
        }
    
        /**
         * 授权
         * @param principalCollection 授权主体
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            System.out.println("开始授权");
            TokenPayload primaryPrincipal = (TokenPayload) principalCollection.getPrimaryPrincipal();
    
            System.out.println(primaryPrincipal.getRoles());
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            //添加角色信息
            simpleAuthorizationInfo.addRole(primaryPrincipal.getRoles());
            //添加权限信息
            simpleAuthorizationInfo.addStringPermission(primaryPrincipal.getPermissions());
    
    
            System.out.println("授权完成");
            return simpleAuthorizationInfo;
        }
    
        @Override
        public Class<?> getAuthenticationTokenClass() {
            return JWTToken.class;
        }
    

    实现自定义的shiroToken

    /**
     * 为了便于使用由JWT生成的token 自定义实现自己的token
     */
    public class JWTToken implements AuthenticationToken {
    
        //存储由请求头中获取的token
        private final String jwtToken;
    
        public JWTToken(String jwtToken) {
            this.jwtToken = jwtToken;
        }
    
        @Override
        public Object getPrincipal() {
            return this.jwtToken;
        }
    
        @Override
        public Object getCredentials() {
            return true;
        }
    }
    

    shiro配置

    @Configuration
    public class ShiroConfig {
    
        /**
         * 1.创建shiroFilterFactoryBean
         * 负责拦截多有请求
         *
         */
        @Bean
        public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager) {
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            //给filter设置安全管理器
            shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
    
            LinkedHashMap<String, Filter> filters = new LinkedHashMap<>();
            filters.put("jwtfilter",new JWTFilter());
            //将自定义过滤器加入到shiro 过滤其中
            shiroFilterFactoryBean.setFilters(filters);
    
            // 完全无状态认证  noSessionCreation 不保留每次会话的session
            //因此每次请求都会进行授权和认证
            HashMap<String, String> map = new HashMap<>();
            map.put("/shiro/login","anon");
            map.put("/shiro/**","noSessionCreation,jwtfilter");
    
            shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
            shiroFilterFactoryBean.setLoginUrl("/shiro/unauthorized");
            return shiroFilterFactoryBean;
        }
    
        /**
         * 2.创建安全管理器
         *
         * @param realm
         * @return
         */
        @Bean
        public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("myRealm") Realm realm) {
            DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
            defaultWebSecurityManager.setRealm(realm);
            return defaultWebSecurityManager;
        }
    
        /**
         * 3.自定义Realm
         *
         * CredentialsMatcher 证书匹配器
         * @return 自定义Realm
         */
        @Bean("myRealm")
        public Realm getRealm() {
            CustomerRealm customerRealm = new CustomerRealm();
            
            
            return customerRealm;
        }
    

    总结

    使用JWTToken与shiro进无状态授权认证时 实际上登录时放弃的使用shiro的认证 登陆时使用自己实现的登录方法并且生成Token,无状态会话,用于shiro不存储每次会话的session 因此每次请求都会进行一次完整的shiro授权认证流程 ,可以使用Redis 等其他缓存的方式实现shiro的缓存 减小系统压力。

  • 相关阅读:
    成为一名优秀程序员所需要知道的那些事
    程序员致富的若干方法探讨
    解决Flex程序在chrome,safari,IE9等浏览器中history的后退与前进问题
    flex4里的状态(state)
    Flex和Flash定时器 setTimeout & setInterval 使用要点
    Flex十种武器(Flex Power Tools: Explorers)
    给你的组件加一个处理事件的属性
    程序员:增加编程经验的另外3种途径
    Flex常用功能代码
    常用Flex IOC框架比较分析
  • 原文地址:https://www.cnblogs.com/huangshen/p/13658912.html
Copyright © 2011-2022 走看看