Spring-boot结合Shrio实现JWT的方法
本文介绍了Spring-boot结合Shrio实现JWT的方法,分享给大家,具体如下:
关于验证大致分为两个方面:
- 用户登录时的验证;
- 用户登录后每次访问时的权限认证
主要解决方法:使用自定义的ShiroFilter
项目搭建:
这是一个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",newJWTFilter());
@Configuration publicclassShiroConfig{ @Bean publicShiroFilterFactoryBeangetShiroFilterFactoryBean(SecurityManagersecurityManager){ ShiroFilterFactoryBeanshiroFilterFactoryBean=newShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); //添加自己的过滤器并且取名为JWTFilter MapfilterMap=newHashMap<>(); filterMap.put("JWTFilter",newJWTFilter()); shiroFilterFactoryBean.setFilters(filterMap); /* *自定义url规则 *http://shiro.apache.org/web.html#urls- */ Map filterChainDefinitionMap=shiroFilterFactoryBean.getFilterChainDefinitionMap(); filterChainDefinitionMap.put("/**","JWTFilter"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); returnshiroFilterFactoryBean; } /** *securityManager不用直接注入shiroDBRealm,可能会导致事务失效 *解决方法见handleContextRefresh *http://www.debugrun.com/a/NKS9EJQ.html */ @Bean("securityManager") publicDefaultWebSecurityManagersecurityManager(TokenRealmtokenRealm){ DefaultWebSecurityManagermanager=newDefaultWebSecurityManager(); manager.setRealm(tokenRealm); /* *关闭shiro自带的session,详情见文档 *http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29 */ DefaultSubjectDAOsubjectDAO=newDefaultSubjectDAO(); DefaultSessionStorageEvaluatordefaultSessionStorageEvaluator=newDefaultSessionStorageEvaluator(); defaultSessionStorageEvaluator.setSessionStorageEnabled(false); subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator); manager.setSubjectDAO(subjectDAO); returnmanager; } @Bean publicLifecycleBeanPostProcessorlifecycleBeanPostProcessor(){ returnnewLifecycleBeanPostProcessor(); } @Bean(name="TokenRealm") @DependsOn("lifecycleBeanPostProcessor") publicTokenRealmtokenRealm(){ returnnewTokenRealm(); } @Bean @DependsOn("lifecycleBeanPostProcessor") publicDefaultAdvisorAutoProxyCreatordefaultAdvisorAutoProxyCreator(){ DefaultAdvisorAutoProxyCreatordefaultAdvisorAutoProxyCreator=newDefaultAdvisorAutoProxyCreator(); //强制使用cglib,防止重复代理和可能引起代理出错的问题 //https://zhuanlan.zhihu.com/p/29161098 defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); returndefaultAdvisorAutoProxyCreator; } @Bean publicAuthorizationAttributeSourceAdvisorgetAuthorizationAttributeSourceAdvisor(SecurityManagersecurityManager){ AuthorizationAttributeSourceAdvisorauthorizationAttributeSourceAdvisor=newAuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); returnnewAuthorizationAttributeSourceAdvisor(); } }
自定义Shriofilter
执行顺序:preHandle->doFilterInternal->executeLogin->onLoginSuccess
主要判断是不是登录请求的是doFilterInternal
publicclassJWTFilterextendsBasicHttpAuthenticationFilter{ /** *自定义执行登录的方法 */ @Override protectedbooleanexecuteLogin(ServletRequestrequest,ServletResponseresponse)throwsIOException{ HttpServletRequesthttpServletRequest=(HttpServletRequest)request; UsernamePasswordTokenusernamePasswordToken=JSON.parseObject(httpServletRequest.getInputStream(),UsernamePasswordToken.class); //提交给realm进行登入,如果错误他会抛出异常并被捕获 Subjectsubject=this.getSubject(request,response); subject.login(usernamePasswordToken); returnthis.onLoginSuccess(usernamePasswordToken,subject,request,response); //错误抛出异常 } /** *最先执行的方法 */ @Override protectedbooleanpreHandle(ServletRequestrequest,ServletResponseresponse)throwsException{ returnsuper.preHandle(request,response); } /** *登录成功后登录的操作 *加上jwt的header */ @Override protectedbooleanonLoginSuccess(AuthenticationTokentoken,Subjectsubject,ServletRequestrequest,ServletResponseresponse){ HttpServletResponsehttpServletResponse=(HttpServletResponse)response; StringjwtToken=Jwts.builder() .setId(token.getPrincipal().toString()) .setExpiration(DateTime.now().plusMinutes(30).toDate()) .signWith(SignatureAlgorithm.HS256,JWTCost.signatureKey) .compact(); httpServletResponse.addHeader(AUTHORIZATION_HEADER,jwtToken); returntrue; } /** *登录以及校验的主要流程 *判断是否是登录,或者是登陆后普通的一次请求 */ @Override publicvoiddoFilterInternal(ServletRequestservletRequest,ServletResponseservletResponse,FilterChainfilterChain)throwsIOException,ServletException{ HttpServletRequesthttpServletRequest=(HttpServletRequest)servletRequest; HttpServletResponsehttpServletResponse=(HttpServletResponse)servletResponse; StringservletPath=httpServletRequest.getServletPath(); if(StringUtils.equals(servletPath,"/login")){ //执行登录 this.executeLogin(servletRequest,servletResponse); }else{ StringauthenticationHeader=httpServletRequest.getHeader(AUTHORIZATION_HEADER); if(StringUtils.isNotEmpty(authenticationHeader)){ Claimsbody=Jwts.parser() .setSigningKey(JWTCost.signatureKey) .parseClaimsJws(authenticationHeader) .getBody(); if(body!=null){ //更新token body.setExpiration(DateTime.now().plusMinutes(30).toDate()); StringupdateToken=Jwts.builder().setClaims(body).compact(); httpServletResponse.addHeader(AUTHORIZATION_HEADER,updateToken); //添加用户凭证 PrincipalCollectionprincipals=newSimplePrincipalCollection(body.getId(),JWTCost.UserNamePasswordRealm);//拼装shiro用户信息 WebSubject.Builderbuilder=newWebSubject.Builder(servletRequest,servletResponse); builder.principals(principals); builder.authenticated(true); builder.sessionCreationEnabled(false); WebSubjectsubject=builder.buildWebSubject(); //塞入容器,统一调用 ThreadContext.bind(subject); filterChain.doFilter(httpServletRequest,httpServletResponse); } }else{ httpServletResponse.setStatus(HttpStatus.FORBIDDEN.value()); } } } }
登录失败处理
处理Shrio异常
@RestControllerAdvice publicclassGlobalControllerExceptionHandler{ @ExceptionHandler(value=Exception.class) publicObjectallExceptionHandler(HttpServletRequestrequest,HttpServletResponseresponse,Exceptionexception){ Stringmessage=exception.getCause().getMessage(); LogUtil.error(message); returnnewResultInfo(exception.getClass().getName(),message); } /*===========Shiro异常拦截==============*/ @ExceptionHandler(value=IncorrectCredentialsException.class) publicStringIncorrectCredentialsException(HttpServletRequestrequest,HttpServletResponseresponse,Exceptionexception){ response.setStatus(HttpStatus.FORBIDDEN.value()); return"IncorrectCredentialsException"; } @ExceptionHandler(value=UnknownAccountException.class) publicStringUnknownAccountException(HttpServletRequestrequest,HttpServletResponseresponse,Exceptionexception){ response.setStatus(HttpStatus.FORBIDDEN.value()); return"UnknownAccountException"; } @ExceptionHandler(value=LockedAccountException.class) publicStringLockedAccountException(HttpServletRequestrequest,HttpServletResponseresponse,Exceptionexception){ response.setStatus(HttpStatus.FORBIDDEN.value()); return"LockedAccountException"; } @ExceptionHandler(value=ExcessiveAttemptsException.class) publicStringExcessiveAttemptsException(HttpServletRequestrequest,HttpServletResponseresponse,Exceptionexception){ response.setStatus(HttpStatus.FORBIDDEN.value()); return"ExcessiveAttemptsException"; } @ExceptionHandler(value=AuthenticationException.class) publicStringAuthenticationException(HttpServletRequestrequest,HttpServletResponseresponse,Exceptionexception){ response.setStatus(HttpStatus.FORBIDDEN.value()); return"AuthenticationException"; } @ExceptionHandler(value=UnauthorizedException.class) publicStringUnauthorizedException(HttpServletRequestrequest,HttpServletResponseresponse,Exceptionexception){ response.setStatus(HttpStatus.FORBIDDEN.value()); return"UnauthorizedException"; } }
处理JWT异常
这是个坑,因为是在filter内发生的异常,@ExceptionHandler是截获不到的。
/** *截获springbootError页面 */ @RestController publicclassGlobalExceptionHandlerimplementsErrorController{ @Override publicStringgetErrorPath(){ return"/error"; } @RequestMapping(value="/error") publicObjecterror(HttpServletRequestrequest,HttpServletResponseresponse)throwsException{ //错误处理逻辑 Exceptionexception=(Exception)request.getAttribute("javax.servlet.error.exception"); Throwablecause=exception.getCause(); if(causeinstanceofExpiredJwtException){ response.setStatus(HttpStatus.GATEWAY_TIMEOUT.value()); returnnewResultInfo("ExpiredJwtException",cause.getMessage()); } if(causeinstanceofMalformedJwtException){ response.setStatus(HttpStatus.FORBIDDEN.value()); returnnewResultInfo("MalformedJwtException",cause.getMessage()); } returnnewResultInfo(cause.getCause().getMessage(),cause.getMessage()); } }
关于权限等授权信息,可以直接放到Redis中实现缓存。我认为也是不错的。
源码奉上:githup-shiro分支:温馨提示:平时测试代码可能比较乱。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。