博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring-boot、Shrio实现JWT
阅读量:7199 次
发布时间:2019-06-29

本文共 10536 字,大约阅读时间需要 35 分钟。

关于验证大致分为两个方面:

  1. 用户登录时的验证;
  2. 用户登录后每次访问时的权限认证

主要解决方法:使用自定义的Shiro Filter

项目搭建:

这是一个spring-boot 的web项目,不了解spring-boot的项目搭建,请google。
  • pom.mx引入相关jar


org.apache.shiro
shiro-spring
${shiro.version}
org.apache.shiro
shiro-core
${shiro.version}
io.jsonwebtoken
jjwt
0.9.0
  • Shrio 的相关配置

划重点!!自定义了一个Filter

filterMap.put("JWTFilter", new JWTFilter());

@Configurationpublic class ShiroConfig {    @Bean    public ShiroFilterFactoryBean getShiroFilterFactoryBean(SecurityManager securityManager) {        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();        shiroFilterFactoryBean.setSecurityManager(securityManager);        // 添加自己的过滤器并且取名为JWTFilter        Map
filterMap = new HashMap<>(); filterMap.put("JWTFilter", new JWTFilter()); shiroFilterFactoryBean.setFilters(filterMap); /* * 自定义url规则 * http://shiro.apache.org/web.html#urls- */ Map
filterChainDefinitionMap = shiroFilterFactoryBean.getFilterChainDefinitionMap(); filterChainDefinitionMap.put("/**", "JWTFilter"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } /** * securityManager 不用直接注入shiroDBRealm,可能会导致事务失效 * 解决方法见 handleContextRefresh * http://www.debugrun.com/a/NKS9EJQ.html */ @Bean("securityManager") public DefaultWebSecurityManager securityManager(TokenRealm tokenRealm) { DefaultWebSecurityManager manager = new DefaultWebSecurityManager(); manager.setRealm(tokenRealm); /* * 关闭shiro自带的session,详情见文档 * http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29 */ DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO(); DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator(); defaultSessionStorageEvaluator.setSessionStorageEnabled(false); subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator); manager.setSubjectDAO(subjectDAO); return manager; } @Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } @Bean(name = "TokenRealm") @DependsOn("lifecycleBeanPostProcessor") public TokenRealm tokenRealm() { return new TokenRealm(); } @Bean @DependsOn("lifecycleBeanPostProcessor") public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); // 强制使用cglib,防止重复代理和可能引起代理出错的问题 // https://zhuanlan.zhihu.com/p/29161098 defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); return defaultAdvisorAutoProxyCreator; } @Bean public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return new AuthorizationAttributeSourceAdvisor(); }}
  • 自定义Shrio filter

执行顺序:preHandle -> doFilterInternal -> executeLogin -> onLoginSuccess

主要判断是不是登录请求的是 doFilterInternal
public class JWTFilter extends BasicHttpAuthenticationFilter {    /**     * 自定义执行登录的方法     */    @Override    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws IOException {        HttpServletRequest httpServletRequest = (HttpServletRequest) request;        UsernamePasswordToken usernamePasswordToken = JSON.parseObject(httpServletRequest.getInputStream(), UsernamePasswordToken.class);        // 提交给realm进行登入,如果错误他会抛出异常并被捕获        Subject subject = this.getSubject(request, response);        subject.login(usernamePasswordToken);        return this.onLoginSuccess(usernamePasswordToken, subject, request, response);        //错误抛出异常    }    /**     * 最先执行的方法     */    @Override    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {        return super.preHandle(request, response);    }    /**     * 登录成功后登录的操作     * 加上jwt 的header     */    @Override    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) {        HttpServletResponse httpServletResponse = (HttpServletResponse) response;        String jwtToken = Jwts.builder()                .setId(token.getPrincipal().toString())                .setExpiration(DateTime.now().plusMinutes(30).toDate())                .signWith(SignatureAlgorithm.HS256, JWTCost.signatureKey)                .compact();        httpServletResponse.addHeader(AUTHORIZATION_HEADER, jwtToken);        return true;    }    /**     * 登录以及校验的主要流程     * 判断是否是登录,或者是登陆后普通的一次请求     */    @Override    public void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;        HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;        String servletPath = httpServletRequest.getServletPath();        if (StringUtils.equals(servletPath, "/login")) {            //执行登录            this.executeLogin(servletRequest, servletResponse);        } else {            String authenticationHeader = httpServletRequest.getHeader(AUTHORIZATION_HEADER);            if (StringUtils.isNotEmpty(authenticationHeader)) {                Claims body = Jwts.parser()                        .setSigningKey(JWTCost.signatureKey)                        .parseClaimsJws(authenticationHeader)                        .getBody();                if (body != null) {                    //更新token                    body.setExpiration(DateTime.now().plusMinutes(30).toDate());                    String updateToken = Jwts.builder().setClaims(body).compact();                    httpServletResponse.addHeader(AUTHORIZATION_HEADER, updateToken);                    //添加用户凭证                    PrincipalCollection principals = new SimplePrincipalCollection(body.getId(), JWTCost.UserNamePasswordRealm);//拼装shiro用户信息                    WebSubject.Builder builder = new WebSubject.Builder(servletRequest, servletResponse);                    builder.principals(principals);                    builder.authenticated(true);                    builder.sessionCreationEnabled(false);                    WebSubject subject = builder.buildWebSubject();                    //塞入容器,统一调用                    ThreadContext.bind(subject);                    filterChain.doFilter(httpServletRequest, httpServletResponse);                }            } else {                httpServletResponse.setStatus(HttpStatus.FORBIDDEN.value());            }        }    }}
  • 登录失败处理

处理Shrio异常

@RestControllerAdvicepublic class GlobalControllerExceptionHandler {    @ExceptionHandler(value = Exception.class)    public Object allExceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception exception) {        String message = exception.getCause().getMessage();        LogUtil.error(message);        return new ResultInfo(exception.getClass().getName(), message);    }    /*=========== Shiro 异常拦截==============*/    @ExceptionHandler(value = IncorrectCredentialsException.class)    public String IncorrectCredentialsException(HttpServletRequest request, HttpServletResponse response, Exception exception) {        response.setStatus(HttpStatus.FORBIDDEN.value());        return "IncorrectCredentialsException";    }    @ExceptionHandler(value = UnknownAccountException.class)    public String UnknownAccountException(HttpServletRequest request, HttpServletResponse response, Exception exception) {        response.setStatus(HttpStatus.FORBIDDEN.value());        return "UnknownAccountException";    }    @ExceptionHandler(value = LockedAccountException.class)    public String LockedAccountException(HttpServletRequest request, HttpServletResponse response, Exception exception) {        response.setStatus(HttpStatus.FORBIDDEN.value());        return "LockedAccountException";    }    @ExceptionHandler(value = ExcessiveAttemptsException.class)    public String ExcessiveAttemptsException(HttpServletRequest request, HttpServletResponse response, Exception exception) {        response.setStatus(HttpStatus.FORBIDDEN.value());        return "ExcessiveAttemptsException";    }    @ExceptionHandler(value = AuthenticationException.class)    public String AuthenticationException(HttpServletRequest request, HttpServletResponse response, Exception exception) {        response.setStatus(HttpStatus.FORBIDDEN.value());        return "AuthenticationException";    }    @ExceptionHandler(value = UnauthorizedException.class)    public String UnauthorizedException(HttpServletRequest request, HttpServletResponse response, Exception exception) {        response.setStatus(HttpStatus.FORBIDDEN.value());        return "UnauthorizedException";    }}

处理JWT异常

这是个坑,因为是在filter内发生的异常,@ExceptionHandler是截获不到的。

/** * 截获spring boot Error页面 */@RestControllerpublic class GlobalExceptionHandler implements ErrorController {    @Override    public String getErrorPath() {        return "/error";    }    @RequestMapping(value = "/error")    public Object error(HttpServletRequest request, HttpServletResponse response) throws Exception {        // 错误处理逻辑        Exception exception = (Exception) request.getAttribute("javax.servlet.error.exception");        Throwable cause = exception.getCause();        if (cause instanceof ExpiredJwtException) {            response.setStatus(HttpStatus.GATEWAY_TIMEOUT.value());            return new ResultInfo("ExpiredJwtException", cause.getMessage());        }        if (cause instanceof MalformedJwtException) {            response.setStatus(HttpStatus.FORBIDDEN.value());            return new ResultInfo("MalformedJwtException", cause.getMessage());        }        return new ResultInfo(cause.getCause().getMessage(), cause.getMessage());    }}

关于权限等授权信息,可以直接放到Redis中实现缓存。我认为也是不错的。

源码奉上: :温馨提示:平时测试代码可能比较乱。

如果更好的实现,让我学习,让我我进步,请联系我。

转载地址:http://shzum.baihongyu.com/

你可能感兴趣的文章
java.sql.SQLException: Io 异常: Got minus one fro...
查看>>
PHP处理字符串
查看>>
数据库根据键值自动判断插入还是更新的SQL
查看>>
RabbitMQ内存与磁盘管理
查看>>
五种情况下Win7或重蹈Vista覆辙
查看>>
Timer定时器
查看>>
Win7、Ubuntu双系统正确卸载Ubuntu系统
查看>>
两数互换的例子
查看>>
我的友情链接
查看>>
网络拓扑自动发现-Sugarnms智能网管软件的基础
查看>>
线程的状态转换图
查看>>
VMware vSphere 5.0 五大改变
查看>>
spring注解性的事物@Transactional不起作用
查看>>
使用aulayout自适应uitableviewcell高度
查看>>
让我们一起Go(三)
查看>>
简单的Linux数据备份方案
查看>>
RIP协议和RIP2
查看>>
linux postgresql 安装配置详解
查看>>
一个监控tomcat运行的脚本分享
查看>>
电脑可以上网但网络连接显示感叹号
查看>>