SpringBoot整合shiro的基本使用

1、编写一个新的认证对象继承自AuthenticationToken,当然也可以直接使用AuthenticationToken

package com.mochenli.eternalstar.shiro.token;
import com.mochenli.eternalstar.common.util.TokenUtil;
import org.apache.shiro.authc.AuthenticationToken;

/**
 * @author: MoChenLi
 * @description:
 * @createTime: 2024-06-08 12:04
 */
public class JwtToken implements AuthenticationToken {//实现shiro默认的token认证类

    private String username;
    private String token;

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

    }
    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

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

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

2、编写一个过滤器JwtFliter继承自AuthenticatingFilter

package com.mochenli.eternalstar.shiro.filter;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.mochenli.eternalstar.common.enums.ExceptionCode;
import com.mochenli.eternalstar.common.exception.BaseException;
import com.mochenli.eternalstar.common.util.ApiResponse;
import com.mochenli.eternalstar.common.util.CookiesUtil;
import com.mochenli.eternalstar.common.util.TokenUtil;
import com.mochenli.eternalstar.shiro.token.JwtToken;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author: MoChenLi
 * @description
 * @createTime: 2024-06-08 13:11
 */
@EqualsAndHashCode(callSuper = true)
@Slf4j
@AllArgsConstructor
@NoArgsConstructor
@Data
public class JwtFilter extends AuthenticatingFilter {

    private String token;
    private boolean enabledTest;

    /**
     * order 1
     * 判断允许进行访问   如果为true就直接跳过其他方法 所以一般设置为false直接进入下一步验证onAccessDenied
     *
     * @param request
     * @param response
     * @param mappedValue
     * @return
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        return false;
    }
    /**
     * order 2
     * 当请求访问受限资源时会调用此方法。它是过滤器执行的核心逻辑,用于处理未授权的请求。
     * 详细解释:
     * 检查请求中是否包含有效的认证信息(如 JWT token)。
     * 如果包含有效的认证信息,调用 executeLogin 方法执行登录。
     * 如果不包含认证信息或认证信息无效,处理异常并返回适当的响应(如 401 未授权)。
     * 当执行executeLogin就会进入createToken方法
     *
     * @param servletRequest
     * @param servletResponse
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
       HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
        httpServletResponse.setContentType("application/json");
        httpServletResponse.setCharacterEncoding("utf-8");
        //允许所有来源
        httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
        // 预检请求的有效期(秒)
        httpServletResponse.addHeader("Access-Control-Max-Age", "3600");
        //允许的请求头
        httpServletResponse.addHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With");
        //允许的HTTP方法
        httpServletResponse.addHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");
        //允许携带凭证
        //httpServletResponse.addHeader("Access-Control-Allow-Credentials", "true");
        return executeLogin(servletRequest, httpServletResponse);
    }
    /**
     * order3
     * 重写执行登录流程
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
            try {
                HttpServletRequest httpServletRequest = (HttpServletRequest) request;
                //通过预检请求  如果是OPTIONS请求,直接返回true,表示放行
                if (httpServletRequest.getMethod().equalsIgnoreCase(OPTIONS_METHOD)) {
                    return true;
                }
                JwtToken token = this.createToken(request, response);
                if (token == null) {
                    throw  new BaseException(ExceptionCode.UNAUTHORIZED.getCode(), "没有凭证!");
                }
                Subject subject = this.getSubject(request, response);
                subject.login(token);
                return this.onLoginSuccess(token, subject, request, response);
            } catch (Exception var5) {
                return this.onLoginFailure( var5, request, response);
            }
    }
    /**
     * order 4
     * 创建token 构建token后跳转到Realm当中的doGetAuthenticationInfo认证方法对token进行认证
     *
     * @param servletRequest
     * @param servletResponse
     * @return
     * @throws Exception
     */
    @Override
    protected JwtToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        //获取请求头当中的token
        String authorToken = httpServletRequest.getHeader("Authorization");
        String tokenToCookies = CookiesUtil.tokenToCookies(httpServletRequest, "token");
        String[] tokens = {authorToken, tokenToCookies};
        String token = TokenUtil.tokenChoice(tokens, "mochenli");
        if(enabledTest&&authorToken!=null&&authorToken.equals(this.token)) {
            JwtToken jwtToken = new JwtToken();
            jwtToken.setToken("swagger");
            jwtToken.setUsername("swaggerTest");
            return jwtToken;
        }
        //判断token为空并且请求类型是Bearer
        if (StrUtil.isNotBlank(token)) {
            return new JwtToken(token);
        }
            return  null;
    }
        /**
         * 认证失败的时候,返回 401 ,认证不通过的 JSON 提示
         *
         * @param e
         * @param request
         * @param response
         * @return
         */
        protected boolean onLoginFailure (Exception e, ServletRequest
        request, ServletResponse response){
            // 设置响应 Header
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            try {
                ApiResponse apiResponse =null;
                // 处理登录失败的异常
                Throwable throwable = e.getCause() == null ? e : e.getCause();
                if(throwable instanceof BaseException){
                    BaseException baseException = (BaseException) throwable;
                    apiResponse  = new ApiResponse(baseException.getStatusCode(), baseException.getMessage(), null);
                }else{
                    apiResponse  = new ApiResponse(ExceptionCode.UNAUTHORIZED.getCode(), throwable.getMessage(), null);
                }
                throwable.printStackTrace();
                String jsonStr = JSONUtil.toJsonStr(apiResponse);
                httpResponse.setStatus(apiResponse.getStatus());
                httpResponse.getWriter().print(jsonStr);
            } catch (IOException ignored) {

            }
            return false;
        }
    }

3、自定义Realm实现授权认证

package com.mochenli.eternalstar.shiro.config;

import cn.hutool.core.date.DateUtil;
import com.mochenli.eternalstar.common.enums.ExceptionCode;
import com.mochenli.eternalstar.common.exception.BaseException;
import com.mochenli.eternalstar.shiro.token.JwtToken;
import com.mochenli.eternalstar.system.entity.SystemToken;
import com.mochenli.eternalstar.system.mapper.SystemTokenMapper;
import com.mochenli.eternalstar.system.service.SystemMenuService;
import com.mochenli.eternalstar.system.service.SystemRoleService;
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.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CompletableFuture;

/**
 * @author: MoChenLi
 * @description:自定义Realm实现授权认证
 * @createTime: 2024-06-06 21:46
 */
@Component
public class ShiroRealm extends AuthorizingRealm  {
    @Autowired
    private SystemMenuService systemMenuService;

    @Autowired
    private SystemRoleService systemRoleService;

    @Autowired
    private SystemTokenMapper systemTokenMapper;



    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }
    /**
     *授权
     * 通过认证后 doGetAuthenticationInfo 传过来认证成功的用户进行授权管理 如果返回为null证明不需要权限
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //获取认证成功传递过来的用户对象
        String userName = (String)principalCollection.getPrimaryPrincipal();
        Set<String> stringSet = new HashSet<>();
        if("swaggerTest".equals(userName)){
            stringSet = systemMenuService.selectMenuAll();
            info.setStringPermissions(stringSet);
            return info;
        }
        boolean rootBoolean = systemRoleService.selectByUserName(userName);
        //如果是未被禁用的超级管理员直接通过
        if(rootBoolean){
         stringSet = systemMenuService.selectMenuAll();
        }else{
            stringSet = systemMenuService.selectMenuByUserId(userName);
        }
        info.setStringPermissions(stringSet);
        return info;
    }

    /**
     * 认证
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        JwtToken token = (JwtToken) authenticationToken;
        if("swagger".equals(token.getToken())&&"swaggerTest".equals(token.getUsername())){
            return new SimpleAuthenticationInfo(token.getUsername(), token.getCredentials(), getName());
        }
        SystemToken systemToken = systemTokenMapper.selectOneById(token.getUsername());
        if(systemToken == null||systemToken.getExpirationTime().before(new Date())){
            throw new BaseException(ExceptionCode.UNAUTHORIZED.getCode(),"凭证过期!");
        }
        //异步更新过期时间
        CompletableFuture.runAsync(() -> {
            systemToken.setExpirationTime(DateUtil.offsetMinute(new Date(), 30));
            systemTokenMapper.insertOrUpdate(systemToken);
        });
        return new SimpleAuthenticationInfo(token.getUsername(), token.getCredentials(), getName());
    }

}

4、定义一个ShiroConfig配置类将安全管理器和过滤器工厂注册成bean,在安全管理器当中设置自定义的Realm,以及将前面定义的过滤器添加自需要授权认证的请求路径

package com.mochenli.eternalstar.shiro.config;

import com.mochenli.eternalstar.knife4j.properties.SpringdocTestProperties;
import com.mochenli.eternalstar.shiro.filter.JwtFilter;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author: MoChenLi
 * @description:
 * @createTime: 2024-06-06 21:48
 */
@Configuration
public class ShiroConfig   {
    @Autowired
    ShiroRealm shiroRealm;

    @Autowired
    SpringdocTestProperties springdocTestProperties;

    /**
     * 定义安全管理器
     * @return
     */
    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(shiroRealm);
        // 关闭shiroDao功能
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        // 不需要将ShiroSession中的东西存到任何地方包括Http Session中)
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);
        return securityManager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        //用shiroFilter工厂进行添加自定义的过滤器
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        Map<String, Filter> filerMap = new LinkedHashMap<>();
        filerMap.put("jwtFiler",new JwtFilter(springdocTestProperties.getToken(),springdocTestProperties.isEnabledTest()));
        //设置过滤器,map
        shiroFilter.setFilters(filerMap);
        //设置安全管理
        shiroFilter.setSecurityManager(securityManager);
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        //authc走默认授权
        //anon不用授权
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/doc.html", "anon");
        filterChainDefinitionMap.put("/v3/api-docs/**", "anon");
        filterChainDefinitionMap.put("/favicon.ico", "anon");
        filterChainDefinitionMap.put("/webjars/**", "anon");
        filterChainDefinitionMap.put("/**", "jwtFiler");
        //设置映射链的规则
        shiroFilter.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilter;

    }


}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

墨辰李

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值