最近和别的软件集成项目,需要提供给别人接口来进行数据传输,发现给他token后并不能访问我的接口,拿postman试了下还真是不行。检查代码发现项目的shiro配置是通过session会话来校验信息的 ,我之前一直是前后端自己写,用浏览器来调试的程序所以没发现这个问题。
浏览器请求头的cookie带着JESSIONID是可以正常访问接口的
那要和别的项目集成,他那边又不是通过浏览器,咋办呢,我这边改造吧,兼容token和session不就行了,下面直接贴改造后的完整代码。
pom加依赖
1 | org.crazycakeshiro-redis2. 4.2 . 1 -RELEASEorg.apache.shiroshiro-all1. 3 .2com.auth0java-jwt3. 2.0 |
1.JwtToken重写token类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | package com.mes.common.token; import com.mes.module.user.dto.SysUserDto; import lombok.Data; import org.apache.shiro.authc.HostAuthenticationToken; import org.apache.shiro.authc.RememberMeAuthenticationToken; import org.apache.shiro.authc.UsernamePasswordToken; @Data public class JwtToken implements HostAuthenticationToken, RememberMeAuthenticationToken { private String token; private char [] password; private boolean rememberMe = false ; private String host; public JwtToken(String token){ this .token = token; } @Override public String getHost() { return null ; } @Override public boolean isRememberMe() { return false ; } @Override public Object getPrincipal() { return token; } @Override public Object getCredentials() { return token; } } |
2.自定义过滤器 JwtFilter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 | package com.mes.common.shiro; import com.alibaba.fastjson.JSON; import com.auth0.jwt.interfaces.Claim; import com.mes.common.token.JwtToken; import com.mes.common.utils.JwtUtils; import com.mes.common.utils.Result; import org.apache.commons.lang3.StringUtils; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.subject.Subject; import org.apache.shiro.web.filter.authc.AuthenticatingFilter; import org.apache.shiro.web.util.WebUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.RequestMethod; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.Map; /** * @Description 自定义过滤器 * @Date 2021/8/18 **/ public class JwtFilter extends AuthenticatingFilter { private static final Logger log = LoggerFactory.getLogger(JwtFilter. class ); @Override protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception { HttpServletRequest request = (HttpServletRequest) servletRequest; String token = request.getHeader( "token" ); if (token == null ){ return null ; } return new JwtToken(token); } /** * 拦截校验 没有登录的情况下会走此方法 * @param servletRequest * @param servletResponse * @return * @throws Exception */ @Override protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; String token = request.getHeader( "token" ); response.setContentType( "application/json;charset=utf-8" ); response.setHeader( "Access-Control-Allow-Credentials" , "true" ); response.setHeader( "Access-control-Allow-Origin" , request.getHeader( "Origin" )); response.setHeader( "Access-Control-Allow-Methods" , "GET,PUT,DELETE,UPDATE,OPTIONS" ); response.setHeader( "Access-Control-Allow-Headers" , request.getHeader( "Access-Control-Request-Headers" )); Subject subject = getSubject(servletRequest,servletResponse); if (!subject.isAuthenticated()){ // 未登录 PrintWriter writer = response.getWriter(); writer.print(JSON.toJSONString( new Result().setCode( 402 ).setMsg( "请先登录" ))); return false ; } if (StringUtils.isEmpty(token)){ PrintWriter writer = response.getWriter(); writer.print(JSON.toJSONString( new Result().setCode( 402 ).setMsg( "请先登录" ))); return false ; } else { // 校验jwt try { Map claimMap = JwtUtils.verifyToken(token); } catch (Exception e) { e.printStackTrace(); PrintWriter writer = response.getWriter(); writer.write(JSON.toJSONString( new Result().setCode( 402 ).setMsg( "登录失效,请重新登录" ))); return false ; } return executeLogin(servletRequest, servletResponse); } } @Override protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) { HttpServletResponse httpServletResponse = (HttpServletResponse) response; Throwable throwable = e.getCause() == null ? e : e.getCause(); Result result = new Result().err().setMsg(e.getMessage()); String json = JSON.toJSONString(result); try { httpServletResponse.getWriter().print(json); } catch (IOException ioException) { } return false ; } /** * 跨域支持 * @param servletRequest * @param response * @return * @throws Exception */ @Override protected boolean preHandle(ServletRequest servletRequest, ServletResponse response) throws Exception { HttpServletRequest httpRequest = WebUtils.toHttp(servletRequest); HttpServletResponse httpResponse = WebUtils.toHttp(response); if (httpRequest.getMethod().equals(RequestMethod.OPTIONS.name())) { httpResponse.setHeader( "Access-Control-Allow-Credentials" , "true" ); httpResponse.setHeader( "Access-control-Allow-Origin" , httpRequest.getHeader( "Origin" )); httpResponse.setHeader( "Access-Control-Allow-Methods" , "GET,PUT,DELETE,UPDATE,OPTIONS" ); httpResponse.setHeader( "Access-Control-Allow-Headers" , httpRequest.getHeader( "Access-Control-Request-Headers" )); System.out.println(httpRequest.getHeader( "Origin" )); System.out.println(httpRequest.getMethod()); System.out.println(httpRequest.getHeader( "Access-Control-Request-Headers" )); httpResponse.setStatus(HttpStatus.OK.value()); return false ; } HttpServletRequest request = (HttpServletRequest) servletRequest; String token = request.getHeader( "token" ); if (token != null ) { try { // Map claimMap = JwtUtils.verifyToken(token); // String authToken = claimMap.get("token").asString(); JwtToken jwtToken = new JwtToken(token); Subject subject = SecurityUtils.getSubject(); subject.login(jwtToken); return true ; } catch (Exception e) { e.printStackTrace(); log.error( "token失效,请重新登录" ); response.getWriter().print(JSON.toJSONString( new Result().setCode( 402 ).setMsg( "token失效,请重新登录" ))); } return false ; } else { // session方式 return super .preHandle(servletRequest, response); } } /* protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception { HttpServletRequest httpRequest = WebUtils.toHttp(request); HttpServletResponse httpResponse = WebUtils.toHttp(response); if (httpRequest.getMethod().equals(RequestMethod.OPTIONS.name())) { httpResponse.setHeader("Access-Control-Allow-Credentials", "true"); httpResponse.setHeader("Access-control-Allow-Origin", httpRequest.getHeader("Origin")); httpResponse.setHeader("Access-Control-Allow-Methods", "GET,PUT,DELETE,UPDATE,OPTIONS"); httpResponse.setHeader("Access-Control-Allow-Headers", httpRequest.getHeader("Access-Control-Request-Headers")); System.out.println(httpRequest.getHeader("Origin")); System.out.println(httpRequest.getMethod()); System.out.println(httpRequest.getHeader("Access-Control-Request-Headers")); httpResponse.setStatus(HttpStatus.OK.value()); return false; } return super.preHandle(request, response); }*/ } |
3.配置过滤器 ShiroFilterRegisterConfig
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | package com.mes.common.config; import com.mes.common.shiro.JwtFilter; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @Description TODO * @Date 2021/8/19 **/ @Configuration public class ShiroFilterRegisterConfig { @Bean public FilterRegistrationBean shiroLoginFilteRegistration(JwtFilter filter) { FilterRegistrationBean registration = new FilterRegistrationBean(filter); registration.setEnabled( false ); return registration; } } |
4. shiroConfig
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | package com..mes.common.config; import com.baomidou.mybatisplus.extension.api.R; import com..mes.common.constant.ExpTime; import com..mes.common.realm.MyRealm; import com..mes.common.shiro.JwtFilter; import com..mes.common.shiro.MyCredentialsMatcher; import org.apache.shiro.session.mgt.SessionManager; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.crazycake.shiro.RedisCacheManager; import org.crazycake.shiro.RedisManager; import org.crazycake.shiro.RedisSessionDAO; 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 javax.servlet.Filter; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; /** * @Description shiro配置 * @Date 2021/8/18 **/ @Configuration public class ShiroConfig { @Autowired private MyRealm myRealm; @Autowired private MyCredentialsMatcher myCredentialsMatcher; @Value ( "${spring.redis.host}" ) private String redisHost; @Value ( "${spring.redis.port}" ) private Integer redisPort; @Value ( "${spring.redis.timeout}" ) private Integer redisTimeout; // @Bean // public DefaultWebSessionManager sessionManager(@Value("${globalSessionTimeout:3600}") long globalSessionTimeout, RedisManager c){ // DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); // sessionManager.setSessionValidationSchedulerEnabled(true); // sessionManager.setSessionIdUrlRewritingEnabled(false); // sessionManager.setSessionValidationInterval(globalSessionTimeout * 1000); // sessionManager.setGlobalSessionTimeout(globalSessionTimeout * 1000); // sessionManager.setSessionDAO(redisSessionDAO(c)); // return sessionManager; // } // @ConfigurationProperties(prefix="spring.redis") // @Bean // public RedisManager redisManager() { // return new RedisManager(); // } // @Bean // public RedisSessionDAO redisSessionDAO(RedisManager redisManager) { // RedisSessionDAO redisSessionDAO = new RedisSessionDAO(); // redisSessionDAO.setRedisManager(redisManager); // return redisSessionDAO; // } // @Bean // public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){ // // DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator(); // defaultAdvisorAutoProxyCreator.setUsePrefix(true); // // return defaultAdvisorAutoProxyCreator; // } @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager(SessionManager sessionManager, RedisCacheManager redisCacheManager){ DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); myRealm.setCredentialsMatcher(myCredentialsMatcher); defaultWebSecurityManager.setRealm(myRealm); defaultWebSecurityManager.setSessionManager(sessionManager); defaultWebSecurityManager.setCacheManager(redisCacheManager); return defaultWebSecurityManager; } @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager,JwtFilter jwtFilter){ ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager); // JwtFilter jwtFilter = new JwtFilter(); Map filterMap = new HashMap(); filterMap.put( "jwt" ,jwtFilter); shiroFilterFactoryBean.setFilters(filterMap); Map map = new LinkedHashMap(); map.put( "/sys/user/login" , "anon" ); map.put( "/swagger-ui.html**" , "anon" ); map.put( "/v2/api-docs" , "anon" ); map.put( "/swagger-resources/**" , "anon" ); map.put( "/webjars/**" , "anon" ); map.put( "/img/**" , "anon" ); map.put( "/fastdfs/**" , "anon" ); map.put( "/**" , "jwt" ); //取消就不会拦截 shiroFilterFactoryBean.setFilterChainDefinitionMap(map); // shiroFilterFactoryBean.setLoginUrl("http://192.168.18.17:3000"); return shiroFilterFactoryBean; } @Bean public JwtFilter getJwtFilter(){ return new JwtFilter(); } /** * 配置shiro redisManager * 使用的是shiro-redis开源插件 * @return */ @Bean public RedisManager redisManager() { RedisManager redisManager = new RedisManager(); redisManager.setHost(redisHost); redisManager.setPort(redisPort); redisManager.setExpire(Math.toIntExact(ExpTime.expTime)); // 配置缓存过期时间 redisManager.setTimeout(redisTimeout); return redisManager; } @Bean public RedisSessionDAO redisSessionDAO(RedisManager redisManager) { // RedisSessionDAO redisSessionDAO = new RedisSessionDAO(); RedisSessionDAO redisSessionDAO = new RedisSessionDAO(); redisSessionDAO.setRedisManager(redisManager); return redisSessionDAO; } /** * shiro session的管理 */ @Bean public DefaultWebSessionManager redisSessionManager(RedisSessionDAO redisSessionDAO) { DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); sessionManager.setSessionDAO(redisSessionDAO); return sessionManager; } @Bean public RedisCacheManager redisCacheManager(RedisManager redisManager) { RedisCacheManager redisCacheManager = new RedisCacheManager(); redisCacheManager.setRedisManager(redisManager); return redisCacheManager; } // @Bean // public FilterRegistrationBean shiroLoginFilteRegistration(JwtFilter filter) { // FilterRegistrationBean registration = new FilterRegistrationBean(filter); // registration.setEnabled(false); // return registration; // } } |
5.自定义认证逻辑 MyRealm
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | package com.mes.common.realm; import com.auth0.jwt.interfaces.Claim; import com.mes.common.token.JwtToken; import com.mes.common.utils.JwtUtils; import com.mes.module.user.dto.SysUserDto; import com.mes.module.user.service.SysUserService; import lombok.SneakyThrows; import org.apache.shiro.authc.*; 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.HashMap; import java.util.Map; /** * @Description 授权 * @Date 2021/8/18 **/ @Component public class MyRealm extends AuthorizingRealm { @Autowired private SysUserService sysUserService; @Override public boolean supports(AuthenticationToken token) { return token instanceof JwtToken; } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { String username = (String) principalCollection.iterator().next(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); return info; } @SneakyThrows @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { JwtToken jwtToken = (JwtToken) authenticationToken; String token = (String) jwtToken.getPrincipal(); Map claimMap = JwtUtils.verifyToken(token); String username = claimMap.get( "name" ).asString(); Map params = new HashMap(); params.put( "username" , username); SysUserDto userDto = sysUserService.getOne(params); if (userDto == null ){ return null ; } // return new SimpleAuthenticationInfo(userDto,userDto.getPassword(),getName()); return new SimpleAuthenticationInfo(userDto,jwtToken,getName()); } } |
6. token工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | package com.mes.common.utils; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; import com.mes.common.constant.ExpTime; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import lombok.Data; import javax.xml.bind.DatatypeConverter; import java.io.UnsupportedEncodingException; import java.util.Date; import java.util.HashMap; import java.util.Map; @Data public class JwtUtils { /** * 加密的秘钥,相当于服务器私钥,一定保管好,不能泄露 */ private static final String secret = "secret" ; /** * token的有效时间,不需要自己验证失效,当失效后,会自动抛出异常 */ public static final Long expTime = ExpTime.expTime; public static String createToken( long id, String name, long loginId) throws UnsupportedEncodingException { Map map = new HashMap(); map.put( "alg" , "HS256" ); map.put( "typ" , "JWT" ); String token = JWT.create() .withHeader(map) .withClaim( "id" , id) .withClaim( "name" , name) .withClaim( "loginId" , loginId) .withExpiresAt( new Date(System.currentTimeMillis() + expTime)) .withIssuedAt( new Date()) .sign(Algorithm.HMAC256(secret)); return token; } public static Map verifyToken(String token) throws UnsupportedEncodingException { JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret)).build(); DecodedJWT jwt = null ; try { jwt = verifier.verify(token); } catch (Exception e) { throw new RuntimeException( "登录凭证已过期,请重新登录" ); } return jwt.getClaims(); } public static Map getClaims(String token) throws UnsupportedEncodingException { JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret)).build(); DecodedJWT jwt = null ; jwt = verifier.verify(token); return jwt.getClaims(); } } |
7.密码验证器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | package com.mes.common.shiro; import com.mes.common.token.JwtToken; import com.mes.common.utils.CommonsUtils; import com.mes.module.user.dto.SysUserDto; import com.mes.module.user.service.SysUserService; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.credential.SimpleCredentialsMatcher; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.Map; /** * @Description 密码验证器 * @Date 2021/8/18 **/ @Component public class MyCredentialsMatcher extends SimpleCredentialsMatcher { @Autowired private SysUserService sysUserService; @Override public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { JwtToken jwtToken = (JwtToken) token; if (jwtToken.getPassword() == null ){ return true ; } String inPassword = new String(jwtToken.getPassword()); SysUserDto dto = (SysUserDto) info.getPrincipals(); String username = dto.getUsername(); String dbPassword = String.valueOf(info.getCredentials()); Map params = new HashMap(); params.put( "username" ,username); SysUserDto dbUser = sysUserService.getOne(params); String salt = dbUser.getSalt(); if (CommonsUtils.encryptPassword(inPassword,salt).equals(dbPassword)){ return true ; } else { return false ; } } } |
8.总结
在Spring Boot, Shiro和JWT的项目中,可以同时使用session和token进行身份验证和授权,但通常token用于无状态的RESTful API,而session用于长连接的情况,如Web应用。