SpringMVC中整合Shiro自定义过滤器
提示:这里可以添加本文要记录的大概内容:
一、 在项目pom中引入shiro依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
二、shiro核心配置类ShiroConfig中添加自定义过滤器
ShiroConfig.java代码如下:
/**
* Shiro的过滤器链
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
/**
* 默认的登陆访问url
*/
shiroFilter.setLoginUrl("/login");
/**
* 登陆成功后跳转的url
*/
shiroFilter.setSuccessUrl("/");
/**
* 没有权限跳转的url
*/
shiroFilter.setUnauthorizedUrl("/global/error");
/**
* 覆盖默认的user拦截器(默认拦截器解决不了ajax请求 session超时的问题,若有更好的办法请及时反馈作者)
*/
HashMap<String, Filter> myFilters = new HashMap<>();
myFilters.put("authc", new AuthFilter());//自定义拦截器
shiroFilter.setFilters(myFilters);//添加自定义拦截器
/**
* 配置shiro拦截器链
*
* anon 不需要认证
* authc 需要认证
* user 验证通过或RememberMe登录的都可以
*
* 当应用开启了rememberMe时,用户下次访问时可以是一个user,但不会是authc,因为authc是需要重新认证的
*
* 顺序从上到下,优先级依次降低
*
*/
Map<String, String> hashMap = new LinkedHashMap<>();
hashMap.put("/static/**", "anon");
hashMap.put("/login", "anon");
hashMap.put("/loginApp", "anon");
hashMap.put("/app/login", "anon");
hashMap.put("/global/sessionError", "anon");
hashMap.put("/kaptcha", "anon");
hashMap.put("/carServiceProvider/h5/**", "anon");
hashMap.put("/miniProgram/appLogin", "anon");//自定义登陆接口直接放行
hashMap.put("/miniProgram/**", "authc");//该请求路径下所有接口都需走自定义拦截器
shiroFilter.setFilterChainDefinitionMap(hashMap);
return shiroFilter;
}
三、自定义过滤器配置
AuthFilter.java代码如下:
package com.tythin.tyboot.core.intercept;
import com.baomidou.mybatisplus.toolkit.StringUtils;
import com.tythin.tyboot.config.properties.JwtProperties;
import com.tythin.tyboot.constant.StatusCode;
import com.tythin.tyboot.core.base.tips.ErrorTip;
import com.tythin.tyboot.core.common.exception.BizExceptionEnum;
import com.tythin.tyboot.core.constant.RedisKey;
import com.tythin.tyboot.core.util.JwtTokenUtil;
import com.tythin.tyboot.core.util.RenderUtil;
import com.tythin.tyboot.core.util.SpringBeanFactoryUtils;
import io.jsonwebtoken.JwtException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
/**
* 对客户端请求的jwt token验证过滤器
*/
public class AuthFilter extends OncePerRequestFilter {
//在spring中,filter都默认继承OncePerRequestFilter,因为能够确保在一次请求只通过一次filter,而不需要重复执行
private final Log logger = LogFactory.getLog(this.getClass());
@Autowired
private JwtTokenUtil jwtTokenUtil;//注入jwt token工具类
@Autowired
private JwtProperties jwtProperties;//注入jwt配置类
@Autowired
private StringRedisTemplate stringRedisTemplate;//注入StringRedisTemplate(用来验证token是否失效)
@Override//重写doFilterInternal方法
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
//此处响应头作用为允许跨域访问
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST,GET,PUT,OPTIONS,DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Origin,X-Requested-With,Content-Type,Accept,Authorization,token");
//判断是否三个Bean是否注入成功,未注入成功重新注入,PS:作者使用@Autowired注入不成功才使用该方法注入(getByType方式)
if (jwtTokenUtil == null || jwtProperties == null || stringRedisTemplate == null)
{
jwtTokenUtil = SpringBeanFactoryUtils.getBean(JwtTokenUtil.class);
jwtProperties = SpringBeanFactoryUtils.getBean(JwtProperties.class);
stringRedisTemplate = SpringBeanFactoryUtils.getBean(StringRedisTemplate.class);
}
//doGet(request,response);//该方法用来获取请求头参数,测试时使用,后来没有需要所以注释
//判断是否为OPTIONS请求,并返回响应头
if (request.getMethod().equals("OPTIONS")) {
response.setStatus(HttpServletResponse.SC_OK);
chain.doFilter(request, response);
}
//从请求头中获取Token
final String requestHeader = request.getHeader(jwtProperties.getHeader());
if (requestHeader != null) {
String authToken = null;
try {
authToken = requestHeader;
if (StringUtils.isEmpty(authToken)) {
RenderUtil.renderJson(response, new ErrorTip(StatusCode.TOKEN_EXPIRED.getCode(),
BizExceptionEnum.TOKEN_EXPIRED.getMessage()));
return;
}
if (authToken.equals("none")) {
chain.doFilter(request, response);
return;
}
if (!stringRedisTemplate.hasKey(RedisKey.ZG_TOKEN + authToken)) {
RenderUtil.renderJson(response, new ErrorTip(StatusCode.TOKEN_EXPIRED.getCode(),
BizExceptionEnum.TOKEN_EXPIRED.getMessage()));
return;
}
} catch (Exception e) {
// 有异常就是requestHeader解析失败
logger.error("异常requestHeader解析失败!");
RenderUtil.renderJson(response, new ErrorTip(StatusCode.TOKEN_EXPIRED.getCode(),
BizExceptionEnum.TOKEN_EXPIRED.getMessage()));
return;
}
// 验证token是否过期,包含了验证jwt是否正确
try {
//作者使用的是redis中存入token
boolean flag = jwtTokenUtil.isTokenExpired(authToken);
if (flag) {
RenderUtil.renderJson(response, new ErrorTip(StatusCode.TOKEN_EXPIRED.getCode(),
StatusCode.TOKEN_EXPIRED.getMsg()));
return;
}
} catch (JwtException e) {
// 有异常就是token解析失败
logger.error("异常token解析失败!");
RenderUtil.renderJson(response, new ErrorTip(StatusCode.TOKEN_EXPIRED.getCode(),
BizExceptionEnum.TOKEN_EXPIRED.getMessage()));
return;
}
} else {
logger.error("requestHeader为空!");
// requestHeader为空
RenderUtil.renderJson(response,
new ErrorTip(StatusCode.TOKEN_EXPIRED.getCode(), StatusCode.TOKEN_EXPIRED.getMsg()));
return;
}
chain.doFilter(request, response);
}
//获取请求头所有内容,输出控制台
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
//获取请求头信息
Enumeration headerNames = request.getHeaderNames();
//使用循环遍历请求头,并通过getHeader()方法获取一个指定名称的头字段
while (headerNames.hasMoreElements()){
String headerName = (String) headerNames.nextElement();
System.out.println(headerName + " : " + request.getHeader(headerName) + "<br/>");
}
}
}
四、其他工具类代码
1.SpringBeanFactoryUtils.java代码如下(注入Bean):
package com.tythin.tyboot.core.util;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class SpringBeanFactoryUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
if (SpringBeanFactoryUtils.applicationContext == null) {
SpringBeanFactoryUtils.applicationContext = applicationContext;
}
}
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
//根据名称(@Resource 注解)
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
//根据类型(@Autowired)
public static <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
public static <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);
}
}
2.JwtTokenUtil.java代码如下(jwt token工具类):
package com.tythin.tyboot.core.util;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.tythin.tyboot.config.properties.JwtProperties;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.auth0.jwt.JWT;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* <p>
* jwt token工具类
* </p>
*/
@Component
public class JwtTokenUtil {
@Autowired
private JwtProperties jwtProperties;
/**
* 获取用户名从token中
*/
public String getUsernameFromToken(String token) {
return getClaimFromToken(token).getSubject();
}
/**
* 获取jwt发布时间
*/
public Date getIssuedAtDateFromToken(String token) {
return getClaimFromToken(token).getIssuedAt();
}
/**
* 获取jwt失效时间
*/
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token).getExpiration();
}
/**
* 获取jwt接收者
*/
public String getAudienceFromToken(String token) {
return getClaimFromToken(token).getAudience();
}
/**
* 获取私有的jwt claim
*/
public String getPrivateClaimFromToken(String token, String key) {
return getClaimFromToken(token).get(key).toString();
}
/**
* 获取md5 key从token中
*/
public String getMd5KeyFromToken(String token) {
return getPrivateClaimFromToken(token, jwtProperties.getMd5Key());
}
/**
* 获取jwt的payload部分
*/
public Claims getClaimFromToken(String token) {
return Jwts.parser().setSigningKey(jwtProperties.getSecret()).parseClaimsJws(token).getBody();
}
/**
* 解析token是否正确,不正确会报异常<br>
*/
public void parseToken(String token) throws JwtException {
Jwts.parser().setSigningKey(jwtProperties.getSecret()).parseClaimsJws(token).getBody();
}
/**
* <pre>
* 验证token是否失效
* true:过期 false:没过期
* </pre>
*/
public Boolean isTokenExpired(String token) {
try {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
} catch (ExpiredJwtException expiredJwtException) {
return true;
}
}
/**
* 生成token(通过用户名和签名时候用的随机数)
*
* @param userId
* @param userName
* @param deviceId
* @param mobile
* @param key
* @param randomKey
* @param channelNo
* @return
* @throws UnsupportedEncodingException
*/
public String generateToken(String userId, String userName, String deviceId, String mobile, String key,
String randomKey, String channelNo, String version) throws UnsupportedEncodingException {
Map<String, Object> claims = new HashMap<>();
claims.put(jwtProperties.getMd5Key(), randomKey);
claims.put("channelNo", channelNo);
claims.put("account", userName);
claims.put("userId", userId);
claims.put("deviceId", deviceId);
claims.put("mobile", mobile);
claims.put("key", key);
//claims.put("version", version);
return doGenerateToken(claims, userName);
}
/**
* 生成token(通过用户名和签名时候用的随机数)
*/
public String generateToken(String userName, String randomKey, String channelNo)
throws UnsupportedEncodingException {
Map<String, Object> claims = new HashMap<>();
claims.put(jwtProperties.getMd5Key(), randomKey);
claims.put("channelNo", channelNo);
claims.put("account", userName);
return doGenerateToken(claims, userName);
}
/**
* 生成token(通过用户名和签名时候用的随机数)
*/
public String generateToken(String userName, String randomKey) {
Map<String, Object> claims = new HashMap<>();
claims.put(jwtProperties.getMd5Key(), randomKey);
return doGenerateToken(claims, userName);
}
/**
* 生成token(通过用户名和签名时候用的随机数)(后台管理系统)
*/
public String generateToken(String userId, String account,String userName,String randomKey) throws UnsupportedEncodingException {
Map<String, Object> claims = new HashMap<>();
claims.put(jwtProperties.getMd5Key(), randomKey);
claims.put("userId", userId);
claims.put("account", account);
claims.put("userName", userName);
return doGenerateToken(claims, account);
}
/**
* 生成token
*/
private String doGenerateToken(Map<String, Object> claims, String subject) {
final Date createdDate = new Date();
final Date expirationDate = new Date(createdDate.getTime() + jwtProperties.getExpiration() * 1000);
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(createdDate)
.setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, jwtProperties.getSecret()).compact();
}
/**
* 获取混淆MD5签名用的随机字符串
*/
public String getRandomKey() {
return ToolUtil.getRandomString(6);
}
/**
* 根据Token获取渠道编号 channel_no
*/
public String getChannelNo(String token) throws UnsupportedEncodingException {
DecodedJWT verifier = JWT.decode(token);
Claim channelNo = verifier.getClaim("channelNo");
return channelNo.asString();
}
/**
* 根据Token获取用户编号 account
*/
public String getAccount(String token) throws UnsupportedEncodingException {
DecodedJWT verifier = JWT.decode(token);
Claim account = verifier.getClaim("account");
return account.asString();
}
/**
* 根据Token获取渠道编号 userId
*/
public String getUserId(String token) throws UnsupportedEncodingException {
DecodedJWT verifier = JWT.decode(token);
Claim userId = verifier.getClaim("userId");
return userId.asString();
}
/**
* 根据Token获取设备编号 deviceId
*/
public String getDeviceId(String token) throws UnsupportedEncodingException {
DecodedJWT verifier = JWT.decode(token);
Claim deviceId = verifier.getClaim("deviceId");
return deviceId.asString();
}
/**
* 根据Token获取手机号 mobile
*/
public String getMobile(String token) throws UnsupportedEncodingException {
DecodedJWT verifier = JWT.decode(token);
Claim mobile = verifier.getClaim("mobile");
return mobile.asString();
}
/**
* 根据Token获取渠道编号 key
*/
public String getKey(String token) throws UnsupportedEncodingException {
DecodedJWT verifier = JWT.decode(token);
Claim key = verifier.getClaim("key");
return key.asString();
}
3.JwtProperties.java代码如下(jwt相关配置):
package com.tythin.tyboot.config.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* jwt相关配置
*
*/
@Configuration
@ConfigurationProperties(prefix = JwtProperties.JWT_PREFIX)
public class JwtProperties {
public static final String JWT_PREFIX = "jwt";
private String header = "Authorization";//请求头中验证字段key
private String secret = "defaultSecret";
// 30天
private Long expiration = 2592000L;
private String md5Key = "randomKey";
public static String getJwtPrefix() {
return JWT_PREFIX;
}
public String getHeader() {
return header;
}
public void setHeader(String header) {
this.header = header;
}
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
public Long getExpiration() {
return expiration;
}
public void setExpiration(Long expiration) {
this.expiration = expiration;
}
public String getMd5Key() {
return md5Key;
}
public void setMd5Key(String md5Key) {
this.md5Key = md5Key;
}
}
至此,Spring整合Shiro自定义过滤器就完结了,如果有疑义,期待与作者沟通。
PS:第一次写博客,有诸多不周到之处,如果对您帮助的话,点个赞再走。
原因
本项目是作者在公司已有SpringMVC整合Shiro项目中,因需求需支持其他平台调用接口,所以才自定义拦截器实现请求拦截。