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-
*/
MapfilterChainDefinitionMap=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(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。