springboot+shiro+jwt

shiro和jwt不明白的查看其他文档,本人不在这里赘述!!

 

1.引用 shiro和jwt

在pom.xml文件中添加引用

复制代码
<!--整合Shiro安全框架-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.3.2</version>
        </dependency>
        <!--集成jwt实现toke-->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.18.1</version>
        </dependency>
复制代码

2.添加shiro配置类

复制代码
package com.ckfuture.pro.config;

import com.ckfuture.pro.shiro.CustomRealm;
import com.ckfuture.pro.shiro.JWTFilter;
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.apache.shiro.mgt.SecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
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;
/**
 * @descrption: shiro配置类
 * @author: CKFuture
 * @since: 2021-09-24 16:15
 * @version: v1.0
 * @LastEditTime:
 * @LastEditors:
 * @copyright: hrbckfuture.com
 */
@Configuration
public class ShiroConfig {
    /**
     * 先经过token过滤器,如果检测到请求头存在 token,则用 token 去 login,接着走 Realm 去验证
     */
    @Bean
    public ShiroFilterFactoryBean factory(SecurityManager securityManager) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();

        // 添加自己的过滤器并且取名为jwt
        Map<String, Filter> filterMap = new LinkedHashMap<>();
        //设置我们自定义的JWT过滤器
        filterMap.put("jwt", new JWTFilter());
        factoryBean.setFilters(filterMap);
        factoryBean.setSecurityManager(securityManager);
        // 设置无权限时跳转的 url;
        factoryBean.setUnauthorizedUrl("/unauthorized/无权限");
        Map<String, String> filterRuleMap = new HashMap<>();
        // 所有请求通过我们自己的JWT Filter
        filterRuleMap.put("/**", "jwt");
        // 放行不需要权限认证的接口
        //放行Swagger接口
        filterRuleMap.put("/v2/api-docs","anon");
        filterRuleMap.put("/swagger-resources/configuration/ui","anon");
        filterRuleMap.put("/swagger-resources","anon");
        filterRuleMap.put("/swagger-resources/configuration/security","anon");
        filterRuleMap.put("/swagger-ui.html","anon");
        filterRuleMap.put("/webjars/**","anon");
        //放行登录接口和其他不需要权限的接口
        filterRuleMap.put("/sys/userinfo/login", "anon");
        filterRuleMap.put("/unauthorized/**", "anon");

        factoryBean.setFilterChainDefinitionMap(filterRuleMap);
        return factoryBean;

    }

    /**
     * 注入 securityManager
     */
    @Bean
    public SecurityManager securityManager(CustomRealm customRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置自定义 realm.
        securityManager.setRealm(customRealm);

        /*
         * 关闭shiro自带的session
         */
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);
        return securityManager;
    }

    /**
     * 添加注解支持
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        // 强制使用cglib,防止重复代理和可能引起代理出错的问题
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

}
复制代码

3.自定义过滤器,对token进行处理

复制代码
package com.ckfuture.pro.shiro;

import com.ckfuture.pro.shiro.JWTToken;
import com.ckfuture.util.ResponseCode;
import com.ckfuture.util.ResponseMsg;
import com.ckfuture.util.Result;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
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.net.URLEncoder;


/**
 * @descrption: 自定义过滤器,对token进行处理
 * @author: CKFuture
 * @since: 2021-09-24 14:47
 * @version: v1.0
 * @LastEditTime:
 * @LastEditors:
 * @copyright: hrbckfuture.com
 */
public class JWTFilter extends BasicHttpAuthenticationFilter {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 如果带有 token,则对 token 进行检查,否则直接通过
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws UnauthorizedException {
        //判断请求的请求头是否带上 "token"
        if (isLoginAttempt(request, response)) {
            //如果存在,则进入 executeLogin 方法执行登入,检查 token 是否正确
            try {
                executeLogin(request, response);
                return true;
            } catch (Exception e) {
                //token 错误
                responseError(response, e.getMessage());
            }
        }
        //如果请求头不存在 token,则可能是执行登陆操作或者是游客状态访问,无需检查 token,直接返回 true
        return true;
    }

    /**
     * 判断用户是否想要登入。authorization : token  数值
     * 检测 header 里面是否包含 token 字段
     */
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        try {
            HttpServletRequest req = (HttpServletRequest) request;

            String token = req.getHeader("authorization").replace("token","").trim();
            return token != null;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 执行登陆操作
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = httpServletRequest.getHeader("authorization").replace("token","").trim();
        JWTToken jwtToken = new JWTToken(token);
        // 提交给realm进行登入,如果错误它会抛出异常并被捕获
        getSubject(request, response).login(jwtToken);
        // 如果没有抛出异常则代表登入成功,返回true
        return true;
    }

    /**
     * 对跨域提供支持
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) 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"));
        // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }

    /**
     * 将非法请求跳转到 /unauthorized/**
     */
    private void responseError(ServletResponse response, String message) {
        try {
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
            //设置编码,否则中文字符在重定向时会变为空字符串
            message = URLEncoder.encode(message, "UTF-8");
            httpServletResponse.sendRedirect("/unauthorized/error/"+message);
        } catch (IOException e) {
            logger.error(e.getMessage());
        }
    }



}
复制代码

4.TOKEN扩展类

复制代码
package com.ckfuture.pro.shiro;

import org.apache.shiro.authc.AuthenticationToken;
/**
 * @descrption: TOKEN扩展类
 * @author: CKFuture
 * @since: 2021-09-24 16:16
 * @version: v1.0
 * @LastEditTime:
 * @LastEditors:
 * @copyright: hrbckfuture.com
 */
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;
    }

}
复制代码

5.JWT工具类

 

复制代码
package com.ckfuture.pro.shiro;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;

import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * @descrption: JWT工具类
 * @author: CKFuture
 * @since: 2021-09-24 16:17
 * @version: v1.0
 * @LastEditTime:
 * @LastEditors:
 * @copyright: hrbckfuture.com
 */
public class JWTUtil {
    // 过期时间 24 小时  60 * 24 * 60 * 1000
    private static final long EXPIRE_TIME = 15 * 60 * 1000;//15分钟
    // 密钥
    private static final String SECRET = "hrbckfuture";
    private static  String json="";

    /**
     * 生成 token
     */
    public static String createToken(String userId) {
        try {
            // 设置过期时间
            Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
            // 私钥和加密算法
            Algorithm algorithm = Algorithm.HMAC256(SECRET);
            // 设置头部信息
            Map<String, Object> header = new HashMap<>(2);
            header.put("Type", "Jwt");
            header.put("alg", "HS256");

            // 返回token字符串 附带userId信息
            return JWT.create()
                    .withHeader(header)
                    .withClaim("userId", userId)
                    //到期时间
                    .withExpiresAt(date)
                    //创建一个新的JWT,并使用给定的算法进行标记
                    .sign(algorithm);

        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 校验 token 是否正确
     */
    public static boolean verify(String token, String userId) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(SECRET);
            //在token中附带了username信息
            JWTVerifier verifier = JWT.require(algorithm)
                    .withClaim("userId", userId)
                    .build();
            //验证 token
            verifier.verify(token);
            return true;

        } catch (Exception exception) {
            return false;
        }
    }

    /**
     * 获得token中的信息,无需secret解密也能获得
     */
    public static String getUserId(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("userId").asString();


        } catch (JWTDecodeException e) {
            return null;
        }
    }


    /**
     * 获取请求头中的 token
     * @param request
     * @return
     */
    public static String get_token(HttpServletRequest request) {
        try{
            Enumeration<String> headerNames = request.getHeaderNames();
            //根据名称获取请求头的值
            while(headerNames.hasMoreElements()){
                String name = headerNames.nextElement();
                if(name.equals("authorization")){
                    json=request.getHeader(name).replace("token","").trim();
                }
            }
            return json;
        }catch (Exception ex){
            System.out.println(ex.getMessage());
            return "";
        }
    }


    /**
     获取精确到毫秒的时间戳
     * @param date
     * @return
     **/
    public static Long getTimestamp(Date date) {
        if (null == date) {
            return (long) 0;
        }
        String timestamp = String.valueOf(date.getTime());
        return Long.valueOf(timestamp);
    }

}
复制代码

6.自定义Realm,实现Shiro安全认证

复制代码
package com.ckfuture.pro.shiro;

import com.ckfuture.pro.shiro.JWTToken;
import com.ckfuture.pro.shiro.JWTUtil;
import com.ckfuture.pro.sys.userinfo.service.userinfoService;
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;
/**
 * @descrption: 自定义Realm,实现Shiro安全认证
 * @author: CKFuture
 * @since: 2021-09-24 16:18
 * @version: v1.0
 * @LastEditTime:
 * @LastEditors:
 * @copyright: hrbckfuture.com
 */
@Component
public class CustomRealm extends AuthorizingRealm {

    @Autowired
    private userinfoService userService;//用户信息接口

    /**
     * 必须重写此方法,不然会报错
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JWTToken;
    }

    /**
     * 默认使用此方法进行用户信息正确与否验证,错误抛出异常即可。
     * 自定义后续方法
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String token = (String) authenticationToken.getCredentials();
        // 解密获得userId,用于和数据库进行对比
        String userId = JWTUtil.getUserId(token);

        if (userId == null || !JWTUtil.verify(token, userId)) {
            throw new AuthenticationException("token认证失败!");
        }

        /* 以下数据库查询可根据实际情况,可以不必再次查询,这里我两次查询会很耗资源
         * 具体业务判断
         */


//        String password = userService.getPassword(username);
//        if (password == null) {
//            throw new AuthenticationException("该用户不存在!");
//        }
//        int ban = userService.checkUserBanStatus(username);
//        if (ban == 1) {
//            throw new AuthenticationException("该用户已被封号!");
//        }
        return new SimpleAuthenticationInfo(token, token, "MyRealm");
    }

    /**
     * 只有当需要检测用户权限的时候才会调用此方法,例如checkRole,checkPermission之类的
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("————权限认证————");
        String userId = JWTUtil.getUserId(principals.toString());
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //获得该用户角色
        //String role = userService.getRole(username);
        //每个角色拥有默认的权限
       // String rolePermission = userService.getRolePermission(username);
        //每个用户可以设置新的权限
      //  String permission = userService.getPermission(username);
       // Set<String> roleSet = new HashSet<>();
       // Set<String> permissionSet = new HashSet<>();
        //需要将 role, permission 封装到 Set 作为 info.setRoles(), info.setStringPermissions() 的参数
      //  roleSet.add(role);
      //  permissionSet.add(rolePermission);
      //  permissionSet.add(permission);
        //设置该用户拥有的角色和权限
      //  info.setRoles(roleSet);
       // info.setStringPermissions(permissionSet);
        return info;
    }

}
复制代码

7.获取token签名

在用户登录接口中返回token

复制代码
  @ApiOperation(value = "用户登录",httpMethod = "GET", notes = "获取用户登录信息")
    @ResponseBody
    @RequestMapping(value = {"/login"}, method = RequestMethod.GET)
    public Result login(
            @ApiParam(value = "登录账号", required = true) String jobNO
            ,@ApiParam(value = "登录密码", required = true) String upassword) {
        try {
            //返回用户实体
            entity = service.getOneByJobNOandPassWord(jobNO, upassword);

            //生成签名
            String userId=entity.getId().toString();
            String user_token=  JWTUtil.createToken(userId);

            //返回token
            entity.setToken(user_token);

            return Result.success(entity);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return Result.failure(ResponseCode.ERROR_999, ResponseMsg.QUERY_ERROR);
        }
    }
复制代码

其他接口正常写就可以,在过滤器中就有token和权限的验证。

 

 其他请求头中携带 token

 

posted @   创客未来  阅读(694)  评论(0编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示