笔记 ── springboot+jwt+shiro 认证授权

本文介绍JWT(JSON Web Token)与Apache Shiro框架结合实现的无状态认证授权机制。JWT提供了一种安全的、自包含的方式在分布式环境中传递用户信息,而Shiro则是一个强大的Java安全框架,用于身份验证、授权、密码管理和会话控制。文章深入讲解了JWT的构成、工作流程,以及如何使用Shiro进行认证和授权,包括自定义Realm和Filter。
摘要由CSDN通过智能技术生成

JWT

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).定义了一种简洁的,自包含的方法用于通信双方之间以JSON对象的形式安全的传递信息。因为数字签名的存在,这些信息是可信的,JWT可以使用HMAC算法或者是RSA的公私秘钥对进行签名。

基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。

流程:

  • 用户使用用户名密码来请求服务器
  • 服务器进行验证用户的信息
  • 服务器通过验证发送给用户一个token
  • 客户端存储token,并在每次请求时附送上这个token值
  • 服务端验证token值,并返回数据

这个token必须要在每次请求时传递给服务端,它应该保存在请求头里, 另外,服务端要支持CORS(跨来源资源共享)策略,一般我们在服务端这么做就可以了Access-Control-Allow-Origin: *。
流程图

JWT的构成

JWT是由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串。

第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).

header

jwt的头部承载两部分信息:

声明类型,这里是jwt
声明加密的算法 通常直接使用 HMAC SHA256

{
  "alg": "HS256",
  "typ": "JWT"
}

对应base64UrlEncode编码为:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
说明:该字段为json格式。alg字段指定了生成signature的算法,默认值为 HS256,typ默认值为JWT
通过header中声明的加密方式进行加密(该加密是可以对称解密的),构成了第一部分.

playload

载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

标准中注册的声明
公共的声明
私有的声明
标准中注册的声明 (建议但不强制使用) :

iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

公共的声明 :
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

私有的声明 :
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

对应base64UrlEncode编码为:eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ

说明:该字段为json格式,表明用户身份的数据,可以自己自定义字段,很灵活。sub 面向的用户,name 姓名 ,iat 签发时间。例如可自定义示例如下:
通过header中声明的加密方式加密(该加密是可以对称解密的),构成了第一部分.

signature

jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

header (base64后的)
payload (base64后的)
secret

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  123456
)

对应的签名为:keH6T3x1z7mmhKL1T3r9sQdAxxdzB6siemGMr_6ZOwU

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.keH6T3x1z7mmhKL1T3r9sQdAxxdzB6siemGMr_6ZOwU

说明:对header和payload进行base64UrlEncode编码后进行拼接。通过key(这里是123456)进行HS256算法签名。
最终得到的JWT的Token为(header.payload.signature):
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式加上secret组合加密,然后就构成了jwt的第三部分。

JWT与Session的区别

相同点是,它们都是存储用户信息; Session 在服务器端 JWT 在客户端

Session方式存储用户信息的最大问题在于要占用大量服务器内存,增加服务器的开销。

JWT方式将用户状态分散到了客户端中,可以明显减轻服务端的内存压力。

安全相关

secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了

shiro (java安全框架)

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

三个核心组件:

Subject: 即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。

SecurityManager: 它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。

Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。
  从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。
  Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。


项目目录


为了测试认证和授权,创建了3个实体类,分别对应用户、角色和权限

项目流程:
如果是login请求,不经过JwtFilter,直接通过JwtUtls生成token;如果是其他请求,通过JwtFilter判断token是否为空,委托Myrealm对token进行认证授权,认证失败就报错,成功就授予用户角色权限。

maven依赖

<!--引入JWT依赖-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.4.0</version>
        </dependency>
<!--引入shiro依赖-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.1</version>
        </dependency>

JwtToken封装类

import org.apache.shiro.authc.AuthenticationToken;
/**
 * 封装了需要传递的信息
 * 类似UsernamePasswordToken
 */
public class JwtToken implements AuthenticationToken {
    private String jwtoken;
    public JwtToken(String jwtoken) {
        this.jwtoken = jwtoken;
    }
    //获取身份
    @Override
    public Object getPrincipal() {
        return jwtoken;
    }
    //获取凭证
    @Override
    public Object getCredentials() {
        return jwtoken;
    }
}

**JwtUtils工具类:**生成、校验token

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTCreationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Date;

public class JwtUtils {
    private static final long EXPIRE_TIME = 60 * 1000;
    private static final String SECRET = "huangwc";
    /**
     * @Description: 校验token
     * @Date: 2020/8/26 11:24
     **/
    public static boolean verify(String token, String username){
        try{
            //获取加密算法对象(密钥)
            Algorithm algorithm = Algorithm.HMAC256(SECRET);
            //获取JWT 验证对象
            JWTVerifier verifier = JWT.require(algorithm)
                    .withClaim("username",username)
                    .build();
            DecodedJWT jwt = verifier.verify(token);
            return true;
        }catch (Exception e){
            return false;
        }
    }

    /**
     * @Description: 创建token
     * @Date: 2020/8/26 11:25
     **/
    public static String sign(String username){
        try{
            Date data = new Date(System.currentTimeMillis() + EXPIRE_TIME);
            Algorithm algorithm = Algorithm.HMAC256(SECRET);
            return JWT.create()
                    .withClaim("username",username)
                    .withExpiresAt(data)
                    .sign(algorithm);
        }catch (Exception e){
            return null;
        }
    }

    /**
     * @Description: 通过token,获取用户名
     * @Date: 2020/8/26 11:25
     **/
    public static String getUsername(String token){
        if (token == null || "".equals(token)){
            return null;
        }
        try{
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("username").asString();
        }catch (JWTCreationException e){
            return null;
        }
    }
}

JwtFilter

import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
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.util.HashMap;
import java.util.Map;

/**
 * 自定义一个Filter,用来拦截所有的请求判断是否携带Token
 * */
public class JwtFilter extends BasicHttpAuthenticationFilter {
    /**
     * 这里我们详细说明下为什么最终返回的都是true,即允许访问
     * 例如我们提供一个地址 GET /article
     * 登入用户和游客看到的内容是不同的
     * 如果在这里返回了false,请求会被直接拦截,用户看不到任何东西
     * 所以我们在这里返回true,Controller中可以通过 subject.isAuthenticated() 来判断用户是否登入
     * 如果有些资源只有登入用户才能访问,我们只需要在方法上面加上 @RequiresAuthentication 注解即可
     * 但是这样做有一个缺点,就是不能够对GET,POST等请求进行分别过滤鉴权(因为我们重写了官方的方法),但实际上对应用影响不大
     */
    /**
     * @Description: isAccessAllowed()判断是否携带了有效的JwtToken
     * @Date: 2020/8/26 13:39
     **/
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        if (isLoginAttempt(request, response)) {
            try {
                return executeLogin(request, response);
            } catch (Exception e) {
                throw new AuthorizationException("权限不足", e);
            }
        }
        return true;
    }

    /**
     * 判断用户是否想要登入。
     * 检测header里面是否包含Authorization字段即可
     */
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        HttpServletRequest req = (HttpServletRequest) request;
        //判断是否是登录请求
        //与前端约定,要求前端将jwtToken放在请求的Header部分
        //所以以后发起请求的时候就需要在Header中放一个token,值就是对应的Token
        String authorization = req.getHeader("token");
        // 拿到当前Header中Authorization的AccessToken(Shiro中getAuthzHeader方法已经实现)
        //String token = this.getAuthzHeader(request);
        return authorization != null;
    }

    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest req = (HttpServletRequest) request;
        Map<String, String> map = new HashMap<>(2);
        String header = req.getHeader("token");
        JwtToken token = new JwtToken(header);
        // 拿到当前Header中Authorization的AccessToken(Shiro中getAuthzHeader方法已经实现)
        //JwtToken token = new JwtToken(this.getAuthzHeader(request));
        // 提交给realm进行登入,如果错误他会抛出异常并被捕获
        try {
        getSubject(request, response).login(token);
        // 如果没有抛出异常则代表登入成功,返回true
        } catch (Exception e) {
            e.printStackTrace();
            //调用下面的方法向客户端返回错误信息
            return false;
        }
        return true;
    }

    /**
     * 此方法相当于isLoginAttempt()和executeLogin()
     * @Description: onAccessDenied()是没有携带JwtToken的时候进行账号密码登录,登录成功允许访问,登录失败拒绝访问
     * @Date: 2020/8/26 13:40
     **/
    /*@Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String jwt = request.getHeader("Authorization");
        JwtToken jwtToken = new JwtToken(jwt);
        try {
            // 委托 realm 进行登录认证
            //所以这个地方最终还是调用JwtRealm进行的认证
            getSubject(servletRequest, servletResponse).login(jwtToken);
            //也就是subject.login(token)
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        //执行方法中没有抛出异常就表示登录成功
        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);
    }
}

MyRealm

import com.example.jwtshirodemo.dao.UserRepository;
import com.example.jwtshirodemo.entity.Role;
import com.example.jwtshirodemo.entity.User;
import com.example.jwtshirodemo.jwt.JwtToken;
import com.example.jwtshirodemo.jwt.JwtUtils;
import org.apache.shiro.authc.*;
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 java.util.HashSet;
import java.util.Set;

/**
 * doGetAuthenticationInfo() 方法:用来验证当前登录的用户,获取认证信息。
 * doGetAuthorizationInfo() 方法:为当前登录成功的用户授予权限和分配角色。
 */
public class MyRealm extends AuthorizingRealm {

    @Autowired
    private UserRepository userRepository;

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

    /**
     * @Description: 授权
     * @Date: 2020/8/26 14:09
     **/
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        // 获取token
        String token = principalCollection.getPrimaryPrincipal().toString();
        System.out.println("token:" + token);
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        // 给该用户设置角色,角色信息存在 t_role 表中取
        String username = JwtUtils.getUsername(token);
        User user = userRepository.getByUsername(username);
        Set<String> roles = new HashSet<>();
        for (Role role : user.getRoles()){
            roles.add(role.getRolename());
        }
        authorizationInfo.setRoles(roles);
        // 给该用户设置权限,权限信息存在 t_permission 表中取
        authorizationInfo.setStringPermissions(userRepository.getPermissions(roles));
        return authorizationInfo;
    }

    /**
     * @Description: 认证
     * @Date: 2020/8/26 14:09
     **/
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 获取token
        String token = authenticationToken.getPrincipal().toString();
        System.out.println("token:" + token);
        if (token == null) {
            throw new NullPointerException("token 不允许为空");
        }
        String username = JwtUtils.getUsername(token);
        //判断
        if (!JwtUtils.verify(token,username)) {
            throw new UnknownAccountException();
        }
        // 根据用户名从数据库中查询该用户,判断是否真实存在
        User user = userRepository.getByUsername(username);
        if(user != null) {
            // 传入用户名和密码进行身份认证,并返回认证信息
            // 这里返回的是账号密码,但是JwtToken都是jwt字符串。还需要一个该Realm(MyRealm)的类名
            AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(token, token, getName());
            return authcInfo;
        } else {
            return null;
        }
    }
}

ShiroConfig

import com.example.jwtshirodemo.jwt.JwtFilter;
import com.example.jwtshirodemo.shiro.MyRealm;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 {
    private static final Logger logger = LoggerFactory.getLogger(ShiroConfig.class);

    /**
     * 注入自定义的 Realm
     * @return MyRealm
     */
    @Bean
    public MyRealm myAuthRealm() {
        MyRealm myRealm = new MyRealm();
        return myRealm;
    }
    /**
     * 注入安全管理器
     * @return SecurityManager
     */
    @Bean
    public SecurityManager securityManager() {
        // 将自定义 Realm 加进来
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(myAuthRealm());
        logger.info("====securityManager注册完成====");
        securityManager.setRealm(myAuthRealm());
        return securityManager;
    }

    /**
     * 注入 Shiro 过滤器
     * @param securityManager 安全管理器
     * @return ShiroFilterFactoryBean
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        // 定义 shiroFactoryBean
        ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
        // 设置自定义的 securityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 设置默认登录的 URL,身份认证失败会访问该 URL
        shiroFilterFactoryBean.setLoginUrl("/login1");
        // 设置成功之后要跳转的链接
        shiroFilterFactoryBean.setSuccessUrl("/success");
        // 设置未授权界面,权限认证失败会访问该 URL
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");

        //添加jwtFilter注册到shiro的Filter中,指定除了login和logout之外的请求都先经过jwtFilter
        Map<String, Filter> filterMap = new HashMap<>();
        //这个地方其实另外两个filter可以不设置,默认就是
        filterMap.put("jwt", new JwtFilter());
        //filterMap.put("anon", new AnonymousFilter());
        //filterMap.put("logout", new LogoutFilter());
        shiroFilterFactoryBean.setFilters(filterMap);

        // LinkedHashMap 是有序的,进行顺序拦截器配置
        // 拦截器
        Map<String, String> filterRuleMap = new LinkedHashMap<>();
        filterRuleMap.put("/login", "anon");
        filterRuleMap.put("/logout", "logout");
        // “/student” 开头的用户需要角色认证,是“admin”才允许
        filterRuleMap.put("/student*/**", "roles[admin]");
        // “/teacher” 开头的用户需要权限认证,是“user:create”才允许
        filterRuleMap.put("/teacher*/**", "perms[\"user:create\"]");
        filterRuleMap.put("/**", "jwt");
        // 以“/admin” 开头的用户需要身份认证,authc 表示要进行身份认证
        // filterRuleMap.put("/admin*", "anon");
        // 设置 shiroFilterFactoryBean 的 FilterChainDefinitionMap
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterRuleMap);
        return shiroFilterFactoryBean;
    }
}

LoginController

import com.example.jwtshirodemo.dao.UserRepository;
import com.example.jwtshirodemo.entity.User;
import com.example.jwtshirodemo.jwt.JwtUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author: huangwc
 * @Description: 操作测试类
 * @Date: 2020/08/26 14:50:39
 * @Version: 1.0
 */
@Controller
public class LoginController {
    @Autowired
    UserRepository userRepository;

    @RequestMapping("/login")
    public ResponseEntity<Map<String, String>> login(String username, String password) {
        Map<String, String> map = new HashMap<>(2);
        User user = userRepository.getByUsername(username);
        if (user.getUsername().equals(username) && user.getPassword().equals(password)) {
            String token = JwtUtils.sign(username);
            map.put("msg", "登录成功");
            map.put("token", token);
            return ResponseEntity.ok(map);
        }
        map.put("msg", "用户名密码错误");
        return ResponseEntity.ok(map);
    }
    /**
     * 身份认证测试接口
     */
    @RequestMapping("/admin")
    public String admin() {
        return "success";
    }
    /**
     * 角色认证测试接口
     */
    @RequestMapping("/student")
    public String student() {
        return "success";
    }
    /**
     * 权限认证测试接口
     */
    @RequestMapping("/teacher")
    public String teacher() {
        return "success";
    }
}

使用Postman测试接口

登录生成token: 由于在ShiroConfig设置了过滤条件,login请求不用经过JwtFilter过滤器,直接调用JwtUtils生成token
在这里插入图片描述
realm认证授权: 除了login请求不经过JwtFilter,其他请求都要经过JwtFilter,判断token是否存在,然后通过getSubject().login() 委托realm进行认证授予权限。
下图我使用的是teacher角色访问/teacher开头的url,需要的是user.*权限,但realm授予它的权限是student.*所以是这个unauthorized这个页面
在这里插入图片描述
token时间过期报错: token过期时间设置60秒,时间一到token失效
在这里插入图片描述

Github项目链接:https://github.com/smallfatsheep/jwt-shiro-demo.git

可以的,我可以为您提供一个基于Spring Boot、JWTShiro和Redis的例子。这个例子将演示如何使用这些技术实现用户认证授权,并且将用户的状态存储在Redis中。 首先,您需要创建一个Spring Boot项目并添加所需的依赖。 在pom.xml文件中添加以下依赖: ``` <dependencies> <!-- Spring Boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Shiro --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-web-starter</artifactId> <version>1.7.1</version> </dependency> <!-- JWT --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <!-- Redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> </dependencies> ``` 接下来,创建一个名为`JwtUtils`的JWT工具类,用于生成和验证JWT令牌。您可以参考以下代码: ```java import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.util.Date; @Component public class JwtUtils { @Value("${jwt.secret}") private String secret; @Value("${jwt.expiration}") private int expiration; @PostConstruct public void init() { secret = Base64.getEncoder().encodeToString(secret.getBytes()); } public String generateToken(String username) { Date now = new Date(); Date expiryDate = new Date(now.getTime() + expiration * 1000); return Jwts.builder() .setSubject(username) .setIssuedAt(now) .setExpiration(expiryDate) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } public String getUsernameFromToken(String token) { Claims claims = Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); return claims.getSubject(); } public boolean validateToken(String token) { try { Jwts.parser().setSigningKey(secret).parseClaimsJws(token); return true; } catch (Exception e) { return false; } } } ``` 然后,创建一个名为`RedisUtils`的Redis工具类,用于操作Redis。您可以参考以下代码: ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import java.util.concurrent.TimeUnit; @Component public class RedisUtils { @Autowired private RedisTemplate<String, Object> redisTemplate; public void set(String key, Object value, long expiration) { redisTemplate.opsForValue().set(key, value, expiration, TimeUnit.SECONDS); } public Object get(String key) { return redisTemplate.opsForValue().get(key); } public void delete(String key) { redisTemplate.delete(key); } public boolean hasKey(String key) { return redisTemplate.hasKey(key); } } ``` 接下来,创建一个名为`JwtRealm`的Shiro Realm,用于验证JWT令牌和授权。您可以参考以下代码: ```java import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; public class JwtRealm extends AuthorizingRealm { @Autowired private JwtUtils jwtUtils; @Autowired private RedisUtils redisUtils; @Override public boolean supports(AuthenticationToken token) { return token instanceof JwtToken; } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { // TODO: 实现授权逻辑 } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { JwtToken jwtToken = (JwtToken) token; String username = jwtUtils.getUsernameFromToken(jwtToken.getToken()); if (username == null || !jwtUtils.validateToken(jwtToken.getToken())) { throw new AuthenticationException("无效的令牌"); } // TODO: 查询用户信息并返回认证信息 return new SimpleAuthenticationInfo(username, jwtToken.getToken(), getName()); } } ``` 最后,创建一个名为`JwtToken`的Shiro Token,用于封装JWT令牌。您可以参考以下代码: ```java 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; } } ``` 以上是一个基于Spring Boot、JWTShiro和Redis的例子。您可以根据您的需求进行修改和扩展。希望对您有帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值