最近和别的软件集成项目,需要提供给别人接口来进行数据传输,发现给他token后并不能访问我的接口,拿postman试了下还真是不行。检查代码发现项目的shiro配置是通过session会话来校验信息的 ,我之前一直是前后端自己写,用浏览器来调试的程序所以没发现这个问题。
浏览器请求头的cookie带着JESSIONID是可以正常访问接口的
那要和别的项目集成,他那边又不是通过浏览器,咋办呢,我这边改造吧,兼容token和session不就行了,下面直接贴改造后的完整代码。
pom加依赖
org.crazycakeshiro-redis2.4.2.1-RELEASEorg.apache.shiroshiro-all1.3.2com.auth0java-jwt3.2.0
1.JwtToken重写token类型
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
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
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
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
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工具类
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.密码验证器
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应用。