• spring-boot集成8:集成shiro,jwt


    Shrio是一个轻量级的,基于AOP 和 Servlet 过滤器的安全框架。它提供全面的安全性解决方案,同时在 Web 请求级和方法调用级处理身份确认和授权。

    JWT(JSON Web Token)是目前最流行的跨域身份验证解决方案,具有加密和自包含的特性。

    1.maven配置

    <!-- shiro -->
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-spring</artifactId>
                <version>1.3.2</version>
            </dependency>
    
            <!--jwt-->
            <dependency>
                <groupId>com.auth0</groupId>
                <artifactId>java-jwt</artifactId>
                <version>3.2.0</version>
            </dependency>

    2.自定义Token Authentication

    package com.bby.security;
    
    import org.apache.shiro.authc.AuthenticationToken;
    
    /**
     * 自定义jwt类型的token
     *
     * @author: zhangyang
     * @create: 2018/11/28 8:39
     **/
    public class MyJWTToken implements AuthenticationToken {
        private String token;
    
        public MyJWTToken(String token) {
            this.token = token;
        }
    
        @Override
        public Object getPrincipal() {
            return token;
        }
    
        @Override
        public Object getCredentials() {
            return token;
        }
    }

    3.自定义Shiro过滤器

    package com.bby.security;
    
    import com.alibaba.fastjson.JSONObject;
    import com.bby.common.vo.Result;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang3.StringUtils;
    import org.apache.shiro.util.AntPathMatcher;
    import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
    
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import java.io.IOException;
    import java.io.PrintWriter;
    
    
    /**
     * jwt token filter
     *
     * @Author zhangyang
     * @Date 下午 8:42 2018/11/27 0027
     **/
    @Slf4j
    public class MyJWTFilter extends BasicHttpAuthenticationFilter {
    
        private String tokenHeader;
        private String loginUri;
    
        public MyJWTFilter(String tokenHeader, String loginUri) {
            this.tokenHeader = tokenHeader;
            this.loginUri = loginUri;
        }
    
        /**
         * 如果是登录则直接放行;
         * 如果带有 token,则对 token 进行检查
         *
         * @param request
         * @param response
         * @param mappedValue
         * @return
         */
        @Override
        protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
            HttpServletRequest req = (HttpServletRequest) request;
            AntPathMatcher matcher = new AntPathMatcher();
            // 开放登录接口访问
            if (matcher.match(loginUri, req.getRequestURI())) {
                return true;
            }
            // 判断请求的请求头是否带上token
            if (StringUtils.isBlank(req.getHeader(tokenHeader))) {
                return false;
            }
    
            // 如果存在,则进入 executeLogin 方法执行登入,检查 token 是否正确
            try {
                executeLogin(request, response);
            } catch (Exception e) {
                log.error(e.getMessage());
                try {
                    // globalExceptionHandler无法处理filter中的异常,这里手动处理
                    PrintWriter out = response.getWriter();
                    out.print(JSONObject.toJSON(Result.failureWithCode(Result.UNAUTHORIZED, e.getMessage())));
                    out.flush();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
                return false;
            }
            return true;
        }
    
        /**
         * 执行登陆操作
         *
         * @param request
         * @param response
         * @return
         */
        @Override
        protected boolean executeLogin(ServletRequest request, ServletResponse response) {
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            String token = httpServletRequest.getHeader(tokenHeader);
            MyJWTToken jwtToken = new MyJWTToken(token);
            // 提交给realm进行登入,如果错误他会抛出异常并被捕获
            getSubject(request, response).login(jwtToken);
            // 如果没有抛出异常则代表登入成功,返回true
            return true;
        }
    }
    View Code

    4.自定义Shiro Realm

    package com.bby.security;
    
    import com.bby.common.util.RedisRepository;
    import com.bby.mapper.system.SysUserMapper;
    import org.apache.commons.collections4.CollectionUtils;
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.AuthenticationInfo;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authc.SimpleAuthenticationInfo;
    import org.apache.shiro.authz.AuthorizationInfo;
    import org.apache.shiro.authz.SimpleAuthorizationInfo;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    import java.util.List;
    
    /**
     * 实现AuthorizingRealm接口用户用户认证
     *
     * @author: zhangyang
     * @create: 2018/11/24 21:25
     **/
    @Component
    public class MyRealm extends AuthorizingRealm {
    
        @Autowired
        private RedisRepository redisRepository;
    
        @Autowired
        private SysUserMapper sysUserMapper;
    
        /**
         * 获取用户权限信息
         *
         * @param principalCollection
         * @return
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            // 获取token
            String token = (String) principalCollection.getPrimaryPrincipal();
            // 查询用户权限信息
            List<String> permissions = sysUserMapper.getPermissionByUserId(JWTUtil.getUserId(token));
    
            // 只添加权限(角色方式不灵活)
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            if (CollectionUtils.isNotEmpty(permissions)) {
                simpleAuthorizationInfo.addStringPermissions(permissions);
            }
            return simpleAuthorizationInfo;
        }
    
        /**
         * 获取用户认证信息
         *
         * @param authenticationToken
         * @return
         * @throws AuthenticationException
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            // 加这一步的目的是在Post请求的时候会先进认证,然后在到请求
            Object principal = authenticationToken.getPrincipal();
            if (principal == null) {
                return null;
            }
    
            String token = (String) principal;
            // 解密获得username,用于和数据库进行对比
            String claim = JWTUtil.getUserId(token);
            // 验证缓存中的登录状态
            if (claim == null
                    || !JWTUtil.verify(token, claim)
                    || !redisRepository.exists(token)) {
                throw new AuthenticationException("token validation failed");
            }
    
            // 获取用户信息
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(token, token, getName());
            return simpleAuthenticationInfo;
        }
    
        /**
         * 设置支持的token类型为自定义jwtToken
         *
         * @param token
         * @return
         */
        @Override
        public boolean supports(AuthenticationToken token) {
            return token instanceof MyJWTToken;
        }
    
        /**
         * 覆盖验证密码是否匹配的方法,因为在自定义的login方法中已经实现了
         *
         * @param token
         * @param info
         * @throws AuthenticationException
         */
        @Override
        protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
        }
    }
    View Code

    5.登录代码

    package com.bby.controller;
    
    import com.bby.common.vo.Result;
    import com.bby.security.JWTUtil;
    import com.bby.service.system.ISysUserService;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiOperation;
    import io.swagger.annotations.ApiParam;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;
    
    import javax.servlet.http.HttpServletRequest;
    import java.util.Map;
    
    /**
     * 登录/鉴权controller
     *
     * @author: zhangyang
     * @create: 2018/11/25 20:26
     **/
    @Api(tags = "认证授权")
    @RestController
    @RequestMapping("auth")
    public class AuthController {
    
        @Autowired
        private ISysUserService sysUserService;
    
        /**
         * 登录
         *
         * @param authInfo
         * @return
         */
        @ApiOperation("登录")
        @PostMapping("login")
        public Result login(@ApiParam("用户名") @RequestBody Map<String, String> authInfo) {
            return sysUserService.validate(authInfo.get("username"), authInfo.get("password"));
        }
    
        /**
         * 登出
         *
         * @return
         */
        @ApiOperation("登出")
        @PostMapping("logout")
        public Result logout(HttpServletRequest request) {
            // 因为token的方式是无状态的,要实现登出,则需要使用缓存来保存状态
            return sysUserService.logout(JWTUtil.getToken(request));
        }
    
        /**
         * 用户信息
         * 用户名:
         * @return
         */
        @ApiOperation("获取用户信息")
        @GetMapping("info")
        public Result info(HttpServletRequest request) {
            return sysUserService.getAuthInfo(JWTUtil.getUserId(JWTUtil.getToken(request)));
        }
    }
    View Code

    6.shiro配置

    package com.bby.security;
    
    import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
    import org.apache.shiro.mgt.DefaultSubjectDAO;
    import org.apache.shiro.mgt.SecurityManager;
    import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
    import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
    import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
    import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.DependsOn;
    
    import javax.servlet.Filter;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * shiro 配置
     *
     * @author: zhangyang
     * @create: 2018/11/24 21:34
     **/
    @Configuration
    public class ShiroConfiguration {
        @Value("${jwt.token-header}")
        private String tokenHeader;
    
        @Value("${jwt.filter-name}")
        private String jwtFilterName;
    
        @Value("${login.uri}")
        private String loginUri;
    
    
        /**
         * 将自己的验证方式加入容器
         *
         * @return
         */
        @Autowired
        private MyRealm myRealm;
    
        /**
         * 权限管理,配置主要是Realm的管理认证
         *
         * @return
         */
        @Bean
        @DependsOn("myRealm")
        public SecurityManager securityManager() {
            DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
            // 使用自己的realm
            System.out.println(myRealm);
            manager.setRealm(myRealm);
    
            // 关闭shiro自带的session
            DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
            DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
            defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
            subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
            manager.setSubjectDAO(subjectDAO);
            return manager;
        }
    
        /**
         * Filter工厂,设置对应的过滤条件和跳转条件
         *
         * @param securityManager
         * @return
         */
        @Bean
        public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
            ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
            // 添加自己的过滤器并且取名为jwt
            Map<String, Filter> filterMap = new HashMap<>();
            System.out.println(tokenHeader);
            filterMap.put("jwt", new MyJWTFilter(tokenHeader, loginUri));
            factoryBean.setFilters(filterMap);
    
            factoryBean.setSecurityManager(securityManager);
            factoryBean.setUnauthorizedUrl("/401");
    
            Map<String, String> filterRuleMap = new HashMap<>();
            // 所有请求通过我们自己的JWT Filter
            filterRuleMap.put("/**", "jwt");
            // 访问401和404页面不通过我们的Filter
            filterRuleMap.put("/401", "anon");
            factoryBean.setFilterChainDefinitionMap(filterRuleMap);
            return factoryBean;
        }
    
        /**
         * 下面的代码是添加注解支持
         */
        @Bean
        public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
            DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
            // 强制使用cglib,防止重复代理和可能引起代理出错的问题
            // https://zhuanlan.zhihu.com/p/29161098
            defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
            return defaultAdvisorAutoProxyCreator;
        }
    
        @Bean
        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
            AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
            advisor.setSecurityManager(securityManager);
            return advisor;
        }
    }
    View Code
  • 相关阅读:
    1. C/C++项目一
    3. C/C++笔试面试经典题目三
    1. C/C++笔试面试经典题目一
    Win7下C/C++跨平台开发工具IDE的安装之Eclipse-CDT
    Win7下C/C++跨平台开发工具IDE的安装之CodeBlocks
    css
    form表单,选择器
    html介绍
    元类
    事务、视图、触发器、存储过程、函数、数据库的备份
  • 原文地址:https://www.cnblogs.com/zhya/p/9989879.html
Copyright © 2020-2023  润新知