Shiro + Redis + Jwt

image-20210323172957339

0. 导入依赖

 <dependency>
     <groupId>org.crazycake</groupId>
     <artifactId>shiro-redis-spring-boot-starter</artifactId>
     <version>3.3.1</version>
 </dependency>

配置yml

jwt:
  secret: xxxxxx
  expiration: 86400000
  tokenHeader: Authorization

shiro-redis:
  redis-manager:
    host: xxxxxx
    database: xxxxxxxxxx

1. ShiroConfig

  • 配置安全管理器
  • 配置Session管理器
  • 配置缓存
  • 配置过滤路径
package com.yang.server.config.shiro;

import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SessionStorageEvaluator;
import org.apache.shiro.mgt.SubjectDAO;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.apache.shiro.web.filter.authc.AnonymousFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.mgt.DefaultWebSessionStorageEvaluator;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisSessionDAO;
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;

@Configuration
public class ShiroConfig {


    /**
     * shiro过滤器配置
     *
     * @param defaultWebSecurityManager
     * @param shiroFilterChainDefinition
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager,
                                                         ShiroFilterChainDefinition shiroFilterChainDefinition,
                                                         JwtFilter jwtFilter) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
        // 设置过滤器
        Map<String, Filter> filter = new HashMap<>();
        filter.put("jwt", jwtFilter);
        shiroFilterFactoryBean.setFilters(filter);
        // 设置过滤路径
        shiroFilterFactoryBean.setFilterChainDefinitionMap(shiroFilterChainDefinition.getFilterChainMap());
        return shiroFilterFactoryBean;
    }

    /**
     * 过滤路径配置
     *
     * @return
     */
    @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition() {
        DefaultShiroFilterChainDefinition defaultShiroFilterChainDefinition = new DefaultShiroFilterChainDefinition();
        LinkedHashMap<String, String> filterMap = new LinkedHashMap<>();
        filterMap.put("/doc.html", "anon");
        filterMap.put("/admin", "jwt");
        filterMap.put("/login/info", "jwt");
        defaultShiroFilterChainDefinition.addPathDefinitions(filterMap);
        return defaultShiroFilterChainDefinition;
    }

    /**
     * 安全管理器
     *
     * @return
     */
    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager(AccountRealm accountRealm,
                                                               RedisCacheManager redisCacheManager,
                                                               DefaultWebSessionManager defaultWebSessionManager) {
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        // 设置realm
        defaultWebSecurityManager.setRealm(accountRealm);
        // 设置redis缓存
        defaultWebSecurityManager.setCacheManager(redisCacheManager);
        // 设置session管理器
        // 关闭session
        DefaultWebSessionStorageEvaluator defaultWebSessionStorageEvaluator = new DefaultWebSessionStorageEvaluator();
        defaultWebSessionStorageEvaluator.setSessionStorageEnabled(false);
        DefaultSubjectDAO defaultSubjectDAO = new DefaultSubjectDAO();
        defaultSubjectDAO.setSessionStorageEvaluator(defaultWebSessionStorageEvaluator);
        defaultWebSecurityManager.setSubjectDAO(defaultSubjectDAO);

//        defaultWebSecurityManager.setSessionManager(defaultWebSessionManager);
        return defaultWebSecurityManager;
    }

    /**
     * redsi session管理
     *
     * @param redisSessionDAO
     * @return
     */
    @Bean
    public DefaultWebSessionManager defaultWebSessionManager(RedisSessionDAO redisSessionDAO) {
        DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();
        // 设置redis session管理
        defaultWebSessionManager.setSessionDAO(redisSessionDAO);
        return defaultWebSessionManager;
    }

    /**
     * 定制realm注入
     *
     * @return
     */
    @Bean
    public AccountRealm getRealm() {
        AccountRealm accountRealm = new AccountRealm();
        // 开启验证缓存
        accountRealm.setAuthenticationCachingEnabled(true);
        // 开启授权缓存
        accountRealm.setAuthorizationCachingEnabled(true);
        return accountRealm;
    }
}

2. JwtFilter

自定义过滤器,重写过滤规则

package com.yang.server.config.shiro;

import com.alibaba.fastjson.JSON;
import com.google.common.base.Strings;
import com.yang.server.common.lang.Result;
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.springframework.stereotype.Component;
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;

@Component
public class JwtFilter extends AuthenticatingFilter {

    /**
     * 从请求中获取token
     * @param servletRequest
     * @param servletResponse
     * @return
     * @throws Exception
     */
    @Override
    protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        return getToken(servletRequest, servletResponse);
    }


    /**
     * 请求首先进入preHandle中,我们进行跨域请求授权
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
        HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("origin"));
        httpServletResponse.setHeader("Access-control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));

        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name()))
        {
            httpServletResponse.setStatus(org.springframework.http.HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }

    /**
     * 访问被允许,请求过来后首先走这里,若是通过,直接放行,否则走onAccessDenied;
     * 此处一般放行特殊请求,一般进行跨域option请求处理
     * @param request
     * @param response
     * @param mappedValue
     * @return
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        return super.isAccessAllowed(request, response, mappedValue);
    }

    /**
     * isAccessAllowed未通过走这里,这里一般需要进行验证,走realm进行验证
     * @param servletRequest
     * @param servletResponse
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        return executeLogin(servletRequest, servletResponse);
    }


    /**
     * 登录成功后处理
     * @param token
     * @param subject
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
        return super.onLoginSuccess(token, subject, request, response);
    }

    /**
     * 登录失败后处理
     * @param token
     * @param e
     * @param request
     * @param response
     * @return
     */
    @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 = Result.error(throwable.getLocalizedMessage());
        String s = JSON.toJSONString(result);
        try {
            httpServletResponse.setContentType("application/json;charset=utf-8");
            PrintWriter writer = httpServletResponse.getWriter();
            writer.print(s);

        } catch (IOException ioException) {
            ioException.printStackTrace();
        }
        return false;
    }

    /**
     * 工具方法,获取请求头中的token
     * @param servletRequest
     * @param servletResponse
     * @return
     */
    protected JwtToken getToken(ServletRequest servletRequest, ServletResponse servletResponse){
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String token = request.getHeader("Authorization");
        if (Strings.isNullOrEmpty(token)){
            return new JwtToken("");
        }
        return new JwtToken(token);
    }
}

3. 自定义Token

自定义AuthenticationToken,用于Realm验证

package com.yang.server.config.shiro;

import org.apache.shiro.authc.AuthenticationToken;

public class JwtToken implements AuthenticationToken {

    private String token;

    public JwtToken(String token) {
        this.token = token;
    }

    @Override
    public Object getPrincipal() {
        return token;
    }

    @Override
    public Object getCredentials() {
        return token;
    }
}

4. JwtUtil

  • 生成token
  • 验证token是否过期
  • 刷新token
package com.yang.server.config.shiro;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.shiro.authc.AuthenticationToken;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Component
public class JwtTokenUtil {
    private final String CLAIM_KEY_USERNAME = "sub";
    private final String CLAIM_KEY_CREATED = "created";
    @Value("${jwt.secret}")
    private String secret;
    @Value("${jwt.expiration}")
    private Long expiration;

    /**
     * 根据用户信息生成token字符串
     * @param username
     * @return
     */
    public String generateToken(String username){
        HashMap<String, Object> claims = new HashMap<>();
        claims.put(CLAIM_KEY_USERNAME, username);
        claims.put(CLAIM_KEY_CREATED, new Date());
        return generateToken(claims);
    }

    /**
     * 根绝负载生成token
     * @param claims
     * @return
     */
    private String generateToken(Map<String, Object> claims) {
        return Jwts.builder()
                .setClaims(claims)
                .setExpiration(generateExpirationTime())
                .signWith(SignatureAlgorithm.HS256, secret)
                .compact();
    }

    /**
     * 生成过期时间
     * @return
     */
    private Date generateExpirationTime() {
        return new Date(System.currentTimeMillis() + expiration);
    }

    /**
     * 从token中获取用户名
     * @param token
     * @return
     */
    public String getUserNameFromToken(String token){
        String username;
        try{
            Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception e){
            e.printStackTrace();
            return null;
        }
        return username;
    }

    /**
     * 从token中获取荷载
     * @param token
     * @return
     */
    public Claims getClaimsFromToken(String token) {
        return Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();
    }


    /**
     * 判断token是否过期
     * @param token
     * @return
     */
    public boolean isTokenExpired(String token) {
        Date expiredDate = getExpirationDateFromToken(token);
        return expiredDate.before(new Date());
    }

    /**
     * 获取token过期时间
     * @param token
     * @return
     */
    private Date getExpirationDateFromToken(String token) {
        Claims claims = getClaimsFromToken(token);
        return claims.getExpiration();
    }

    /**
     * 判断token是否可以刷新
     * @param token
     * @return
     */
    public boolean canRefresh(String token){
        return isTokenExpired(token);
    }

    public String refresh(String token){
        Claims claims = getClaimsFromToken(token);
        claims.put(CLAIM_KEY_CREATED, new Date());
        return generateToken(claims);
    }

}

5. 自定义Realm

进行验证和授权操作

package com.yang.server.config.shiro;


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.yang.server.mapper.AdminMapper;
import com.yang.server.pojo.Admin;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;

public class AccountRealm extends AuthorizingRealm {

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Autowired
    private AdminMapper adminMapper;

    /**
     * 支持jwt token
     *
     * @param token
     * @return
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    /**
     * 登录认证
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        JwtToken jwtToken = (JwtToken) authenticationToken;
        String token = (String) jwtToken.getPrincipal();
        // 没有token,未登录,防止登录
        if (token.equals("")){
            throw new UnauthenticatedException("未登录");
        }

        // 判断token是否过期
        if (jwtTokenUtil.isTokenExpired(token)) {
            throw new ExpiredCredentialsException("token过期");
        }
        // 未过期,判断是否存在该用户
        String username = jwtTokenUtil.getUserNameFromToken(token);
        System.out.println("AccountRealm===>username:" + username);
        Admin admin = adminMapper.selectOne(new QueryWrapper<Admin>().eq("username", username));
        // 用户不存在
        if (admin == null) {
            throw new UnknownAccountException("用户不存在");
        }
        // 用户存在, 判断用户是否锁定
        System.out.println(admin.getEnabled());
        if (!admin.getEnabled()) {
            throw new LockedAccountException("用户被锁定");
        }
        // 用户未被锁定,放行
        return new SimpleAuthenticationInfo(jwtToken.getPrincipal(), jwtToken.getCredentials(), getName());
    }

    /**
     * 授权
     *
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值