权限控制Shiro+JWT

基本概念

不废话,干代码使用shiro需要一个配置类,我这边是用了springboot。搞了个bean;shiro的三个重要配置

1、Realm:定义自己逻辑,比如这个地方你可去关联你的userService去查询个人角色信息,给框架返回一个角色列表。

2、DefaultWebSecurityManager:可AI查下概念,我的解读是为了我可以使用ShiroFilterFactoryBean定义我的url而存在。

3、ShiroFilterFactoryBean:可以根据url去走过滤器,从而实现控制权限。

JwtFilter.preHandle

解:对跨域提供支持,过滤器链中拦截请求,判断是否为跨域请求

JwtFilter.isAccessAllowed

解:判断是否携带了有效的JwtToken

JwtFilter.executeLogin

解:执行登陆,从Header获取token

getSubject

解:传入到AccountRealm方便做验证动作

 AccountRealm.supports

解:标识这个Realm是专门用来验证JwtToken,不负责验证其他的token(UsernamePasswordToken)

 AccountRealm.doGetAuthenticationInfo

解:

1:获取jwt中关于用户名
2: 判断token是否过期与合法性
3: 放入SimpleAuthenticationInfo,便于下个对象获取

AccountRealm.doGetAuthorizationInfo

解:获取上个方法传递的参数,进行权限分类(不想每次都去查db,于是引入了redis)

ShiroConfig

package com.chinaratings.config;

import com.chinaratings.enums.AccountRealm;
import com.chinaratings.jwt.JwtFilter;
import jakarta.servlet.Filter;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import java.util.HashMap;
import java.util.LinkedHashMap;

/**
 * shiro启用注解拦截控制器
 * shiro的三个重要配置
 * 1、Realm
 * 2、DefaultWebSecurityManager
 * 3、ShiroFilterFactoryBean
 */
@Configuration
public class ShiroConfig {

    @Bean("securityManager")
    public DefaultWebSecurityManager securityManager(@Qualifier("accountRealm") AccountRealm accountRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(accountRealm);
        // 关闭shiroDao功能
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        // 不需要将ShiroSession中的东西存到任何地方包括Http Session中)
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);
        // securityManager.setSubjectFactory(subjectFactory());
        return securityManager;
    }

    @Bean("shiroFilterFactoryBean")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);
        shiroFilter.setFilters(getFilters());
        shiroFilter.setFilterChainDefinitionMap(getFilterChainDefinitionMap());
        return shiroFilter;
    }
    // 提取过滤器链配置到单独方法
    private LinkedHashMap<String, String> getFilterChainDefinitionMap() {
        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // 添加anon路径
        addAnonPaths(filterChainDefinitionMap);
        // 添加需要认证的路径
        addAuthPaths(filterChainDefinitionMap);
        return filterChainDefinitionMap;
    }
    // 添加所有anon路径
    private void addAnonPaths(LinkedHashMap<String, String> filterChainDefinitionMap) {
        filterChainDefinitionMap.put("/user/login*", "anon");
        filterChainDefinitionMap.put("/validatecode.jsp*", "anon");
        filterChainDefinitionMap.put("/css/**", "anon");
        filterChainDefinitionMap.put("/js/**", "anon");
        filterChainDefinitionMap.put("/images/**", "anon");
        filterChainDefinitionMap.put("/webjars/**", "anon");
        filterChainDefinitionMap.put("/swagger-ui.html", "anon");
        filterChainDefinitionMap.put("/v2/api-docs", "anon");
        filterChainDefinitionMap.put("/doc.html", "anon");
        filterChainDefinitionMap.put("/swagger-resources/**", "anon");
        filterChainDefinitionMap.put("/authcode", "anon");
        filterChainDefinitionMap.put("/favicon.ico", "anon");
        filterChainDefinitionMap.put("/csrf", "anon");
        filterChainDefinitionMap.put("/user/verificationCode", "anon");
        filterChainDefinitionMap.put("/user/login", "anon");
        filterChainDefinitionMap.put("/user/forgotPassword", "anon");
    }
    // 添加需要认证的路径
    private void addAuthPaths(LinkedHashMap<String, String> filterChainDefinitionMap) {
        //filterChainDefinitionMap.put("/ims/webFile/**", "roles[admin]");
        ///**作为备选路径,确保所有其他路径都经过jwt过滤器
        filterChainDefinitionMap.put("/**", "jwt");
    }
    // 提取过滤器配置到单独方法
    private HashMap<String, Filter> getFilters() {
        HashMap<String, Filter> filterMap = new HashMap<>();
        filterMap.put("jwt", new JwtFilter());
        // 确保JwtFilter中有完善的异常处理
        return filterMap;
    }

    /**
     * 解决@RequiresAuthentication注解不生效的配置
     */
    @Bean("lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    /**
     * 为Spring-Bean开启对Shiro注解的支持
     */
    @Bean("authorizationAttributeSourceAdvisor")
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

}

AccountRealm

package com.chinaratings.enums;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.chinaratings.config.BaseException;
import com.chinaratings.ims.dto.ResultDTO;
import com.chinaratings.ims.dto.User;
import com.chinaratings.ims.service.UserService;
import com.chinaratings.ims.vo.UserVo;
import com.chinaratings.jwt.JwtToken;
import com.chinaratings.jwt.JwtUtil;
import com.chinaratings.util.RedisUtil;
import io.jsonwebtoken.Claims;
import jakarta.annotation.Resource;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
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.HashSet;
import java.util.Set;

@Component
public class AccountRealm extends AuthorizingRealm {

    @Resource
    private JwtUtil jwtUtil;

    @Autowired
    private UserService userService;

    @Autowired
    private RedisUtil redisUtil;


    /**
     * 多重写一个support
     * 标识这个Realm是专门用来验证JwtToken
     * 不负责验证其他的token(UsernamePasswordToken)
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    /**
     * 认证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String jwt = (String) authenticationToken.getCredentials();
        // 获取jwt中关于用户名
        String username = jwtUtil.getClaimsByToken(jwt).getSubject();
        // 查询用户
        Object obj = redisUtil.get(username);
        if(null == obj){
            throw new BaseException(ResponseCodeEnum.LOGIN_ERROR, "未登录或Token失效");
        }
        // 将JSON字符串转换为用户对象
        UserVo userVo = JSON.parseObject((String)obj, UserVo.class);
        Claims claims = jwtUtil.getClaimsByToken(jwt);
        if (jwtUtil.isTokenExpired(claims.getExpiration())) {
            throw new BaseException(ResponseCodeEnum.LOGIN_ERROR, "token过期,请重新登录");
        }
        return new SimpleAuthenticationInfo(userVo, jwt, getName());
    }

    /**
     * 授权
     * 权限分类:
     * 1:管理员 访问所有资源
     * 2:vip 除管理模块外均可访问
     * 3:normal : 不能下载,不能翻页,不能访问管理模块
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        UserVo userVo = (UserVo)principalCollection.getPrimaryPrincipal();
        Set<String> permsSet = new HashSet<>();
        permsSet.add(userVo.getRoleName());
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setRoles(permsSet);
        return info;
    }
}

JwtFilter

package com.chinaratings.jwt;

import com.alibaba.fastjson.JSON;
import com.chinaratings.enums.ResponseCodeEnum;
import com.chinaratings.ims.dto.ResultDTO;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;

import java.io.IOException;

@Slf4j
@Component
public class JwtFilter extends BasicHttpAuthenticationFilter {

    /**
     * isAccessAllowed()判断是否携带了有效的JwtToken
     * onAccessDenied()是没有携带JwtToken的时候进行账号密码登录,登录成功允许访问,登录失败拒绝访问
     * * 1. 返回true,shiro就直接允许访问url
     * * 2. 返回false,shiro才会根据onAccessDenied的方法的返回值决定是否允许访问url
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) {
        try {
            return executeLogin(servletRequest, servletResponse);
        } catch (Exception e) {
            return false;
        }
    }
    /**
     * 执行登录
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response){
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = httpServletRequest.getHeader(JwtUtil.HEADER);
        if (StringUtils.isEmpty(token)) {
            return true;
        }
        JwtToken jwtToken = new JwtToken(token);
        getSubject(request, response).login(jwtToken);
        return true;
    }

    /**
     * @param servletRequest
     * @param servletResponse
     * @throws Exception
     * @return 返回结果为true表明登录通过
     */
    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
        httpServletResponse.setStatus(ResponseCodeEnum.OK.getCode());
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("applicatioin/json; charset=utf-8");
        try(ServletOutputStream out = httpServletResponse.getOutputStream()){
            out.write(JSON.toJSONString(ResultDTO.error(ResponseCodeEnum.TOKEN_ERROR)).getBytes("utf-8"));
        }catch(IOException e) {
            throw new AuthenticationException("直接返回Response信息出现IOException异常:" + e.getMessage());
        }
        return false;
    }

    /**
     * 对跨域提供支持
     * 过滤器链中拦截请求,判断是否为跨域请求
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        // 判断请求方式是否为跨域请求
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setHeader("Access-Control-Allow-Origin", httpServletRequest.getHeader("Origin"));
            httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
            httpServletResponse.setHeader("Access-Control-Allow-Methods", httpServletRequest.getHeader("Access-Control-Request-Method"));
            httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
            return false;
        }
        // 继续执行过滤器链
        return super.preHandle(request, response);
    }




}

JwtToken

package com.chinaratings.jwt;

import org.apache.shiro.authc.AuthenticationToken;

/**
 * 继承AuthenticationToken,跟AccountRealmh中的doGetAuthenticationInfo的参数类型保持一致
 */
public class JwtToken implements AuthenticationToken {
    private String username;
    private String token;

    public JwtToken(String token){
        this.token = token;
        JwtUtil jwtUtil = new JwtUtil();
        this.username = jwtUtil.getClaimFiled(token, "username");
    }

    /**
     * 类似用户名
     * @return
     */
    @Override
    public Object getPrincipal() {
        return username;
    }

    /**
     * 类似密码
     * @return
     */
    @Override
    public Object getCredentials() {
        return token;
    }
}

JwtUtil

package com.chinaratings.jwt;

import com.auth0.jwt.JWT;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.Map;

@Component
@Slf4j
public class JwtUtil {
    private static final String SECRET = "zxcvbnmfdasaererafafafafafafakjlkjalkfafadffdafadfafafaaafadfadfaf1234567890";
    private static final long EXPIRE = 60 * 24 * 7;
    public static final String HEADER = "Authorization";

    /**
     * 生成jwt token
     */
    public String generateToken(String username) {
        SecretKey signingKey = Keys.hmacShaKeyFor(SECRET.getBytes(StandardCharsets.UTF_8));
        //过期时间
        LocalDateTime tokenExpirationTime = LocalDateTime.now().plusMinutes(EXPIRE);
        return Jwts.builder()
                .signWith(signingKey, Jwts.SIG.HS512)
                .header().add("typ", "JWT").and()
                .issuedAt(Timestamp.valueOf(LocalDateTime.now()))
                .subject(username)
                .expiration(Timestamp.valueOf(tokenExpirationTime))
                .claims(Map.of("username", username))
                .compact();
    }

    public Claims getClaimsByToken(String token) {
        SecretKey signingKey = Keys.hmacShaKeyFor(SECRET.getBytes(StandardCharsets.UTF_8));
        return Jwts.parser()
                .verifyWith(signingKey)
                .build()
                .parseSignedClaims(token)
                .getPayload();
    }

    /**
     * 检查token是否过期
     * @return true:过期
     */
    public boolean isTokenExpired(Date expiration) {
        return expiration.before(new Date());
    }

    /**
     * 获得token中的自定义信息,一般是获取token的username,无需secret解密也能获得
     * @param token
     * @param filed
     * @return
     */
    public String getClaimFiled(String token, String filed){
        try{
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim(filed).asString();
        } catch (JWTDecodeException e){
            log.error("JwtUtil getClaimFiled error: ", e);
            return null;
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值