SpringMVC中整合Shiro自定义过滤器

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项目中,因需求需支持其他平台调用接口,所以才自定义拦截器实现请求拦截。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值