Shiro + JWT 进行登录验证

简介: Shiro + JWT 进行登录验证

Shiro是一个关于java的安全框架,可以实现用户的认证和授权,简单易用。

首先导入依赖

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>4.4.0</version>
        </dependency>

JwtRealm 验证配置

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    String jwt = (String) token.getPrincipal();
    if (jwt == null) {
        throw new NullPointerException("jwtToken 不允许为空");
    }
    // 判断
    if (!jwtUtil.isVerify(jwt)) {
        throw new UnknownAccountException();
    }
    // 可以获取username信息,并做一些处理
    String username = (String) jwtUtil.decode(jwt).get("username");
    logger.info("鉴权用户 username:{}", username);
    return new SimpleAuthenticationInfo(jwt, jwt, "JwtRealm");
}

在 doGetAuthenticationInfo 方法中,使用 jwtUtil.isVerify(jwt) 方法做验证处理。

JwtFilter 过滤器

public class JwtFilter extends AccessControlFilter {
 
    private Logger logger = LoggerFactory.getLogger(JwtFilter.class);
 
    /**
     * isAccessAllowed 判断是否携带有效的 JwtToken
     * 所以这里直接返回一个 false,让它走 onAccessDenied 方法流程
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        return false;
    }
 
    /**
     * 返回结果为true表明登录通过
     */
    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        // 如果你设定的 token 放到 header 中,则可以这样获取;request.getHeader("Authorization");
        JwtToken jwtToken = new JwtToken(request.getParameter("token"));
        try {
            // 鉴权认证
            getSubject(servletRequest, servletResponse).login(jwtToken);
            return true;
        } catch (Exception e) {
            logger.error("鉴权认证失败", e);
            onLoginFail(servletResponse);
            return false;
        }
    }
 
    /**
     * 鉴权认证失败时默认返回 401 状态码
     */
    private void onLoginFail(ServletResponse response) throws IOException {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        httpResponse.getWriter().write("Auth Err!");
    }
 
}

这是一个自定义的 Filter 在 onAccessDenied 获取 request 请求的 token 入参信息,之后调用 getSubject 进行验证处理。

ShiroConfig 启动配置

@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean() {
    ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
    shiroFilter.setSecurityManager(securityManager());
    shiroFilter.setLoginUrl("/unauthenticated");
    shiroFilter.setUnauthorizedUrl("/unauthorized");
    // 添加jwt过滤器
    Map<String, Filter> filterMap = new HashMap<>();
    // 设置过滤器【anon\logout可以不设置】
    filterMap.put("anon", new AnonymousFilter());
    filterMap.put("jwt", new JwtFilter());
    filterMap.put("logout", new LogoutFilter());
    shiroFilter.setFilters(filterMap);
    // 拦截器,指定方法走哪个拦截器 【login->anon】【logout->logout】【verify->jwt】
    Map<String, String> filterRuleMap = new LinkedHashMap<>();
    filterRuleMap.put("/login", "anon");
    filterRuleMap.put("/logout", "logout");
    filterRuleMap.put("/verify", "jwt");
    shiroFilter.setFilterChainDefinitionMap(filterRuleMap);
    return shiroFilter;
}

这部分是一个设置过滤器和拦截处理,把 jwt 的过滤器设置上,之后拦截指定的 /verify 方法。如果是 /** 就是拦截所有除了 login、logout 配置的其他方法了。通常也是 Web 请求的一些配置操作。

ApiAccessController

@RestController
public class ApiAccessController {
    private Logger logger = LoggerFactory.getLogger(ApiAccessController.class);
 
 
    @RequestMapping("/authorize")
    public ResponseEntity<Map<String, String>> authorize(String username, String password) {
        Map<String, String> map = new HashMap<>();
        // 模拟账号和密码校验
        if (!"xyb".equals(username) || !"123".equals(password)) {
            map.put("msg", "用户名密码错误");
            return ResponseEntity.ok(map);
        }
        // 校验通过生成token
        JwtUtil jwtUtil = new JwtUtil();
        Map<String, Object> chaim = new HashMap<>();
        chaim.put("username", username);
        String jwtToken = jwtUtil.encode(username, 24*60 * 60 * 1000, chaim);
        map.put("msg", "授权成功");
        map.put("token", jwtToken);
        // 返回token码
        return ResponseEntity.ok(map);
    }
 
    /**
     * http://localhost:8080/verify?token=
     */
    @RequestMapping("/verify")
    public ResponseEntity<String> verify(String token) {
        logger.info("验证 token:{}", token);
        return ResponseEntity.status(HttpStatus.OK).body("verify success!");
    }
 
    @RequestMapping("/success")
    public String success(){
        return "test success by xfg";
    }
}

专门用于授权分配Token和验证处理的操作。不过这里的登录目前还没有走数据库,只是简单的验证处理。

测试:http://localhost:8080/authorize?username=xyb&password=123 - 你会获得一个 Token 信息。用于访问 http://localhost/api?token=【添加到这里】 - 这个地址是 Nginx 提供的

Nginx配置如下:

location /api/ {
    auth_request /auth;
    # 鉴权通过后的处理方式
    proxy_pass http://localhost:8080/success;
}
location = /auth {
    # 发送子请求到HTTP服务,验证客户端的凭据,返回响应码
    internal;
    # 设置参数
    set $query '';
    if ($request_uri ~* "[^\?]+\?(.*)$") {
        set $query $1;
    }
    # 验证成功,返回200 OK
    proxy_pass http://localhost:8080/verify?$query;
    # 发送原始请求
    proxy_pass_request_body off;
    # 清空 Content-Type
    proxy_set_header Content-Type "";
 }

相关类的解释说明;

  1. JwtToken:Token 的对象信息,你可以设置用户ID、用户密码
  2. JwtRealm:一个自定义的验证服务,需要继承 AuthorizingRealm 类。
  3. JwtFilter:自定义的 Filter 过滤器。
  4. JwtUtil:token的创建、解析、验证工具类。
  5. ShiroConfig:Shiro 的一个配置启动类。
  6. ApiAccessController:新增加的API访问准入管理;当访问 OpenAI 接口时,需要进行准入验证。


目录
相关文章
|
1月前
|
存储 缓存 数据库
【万字长文】微服务整合Shiro+Jwt,源码分析鉴权实战
介绍如何整合Spring Boot、Shiro和Jwt,以实现一个支持RBAC的无状态认证系统。通过生成JWT token,实现用户无状态登录,并能根据用户角色动态鉴权,而非使用Shiro提供的注解,将角色和权限信息硬编码。此外,文章还探讨了如何对Shiro的异常进行统一捕获和处理。作为应届生,笔者在学习Shiro的过程中进行了一些源码分析,尽管可能存在不足和Bug,但希望能为同样需要实现权限管理的开发者提供参考,并欢迎各位大佬指正完善。
194 65
【万字长文】微服务整合Shiro+Jwt,源码分析鉴权实战
|
26天前
|
JSON 算法 Go
go语言后端开发学习(一)——JWT的介绍以及基于JWT实现登录验证
go语言后端开发学习(一)——JWT的介绍以及基于JWT实现登录验证
|
2月前
|
前端开发 Java Spring
SpringBoot通过拦截器和JWT令牌实现登录验证
该文介绍了JWT工具类、匿名访问注解、JWT验证拦截器的实现以及拦截器注册。使用`java-jwt`库生成和验证JWT,JwtUtil类包含generateToken和verifyToken方法。自定义注解`@AllowAnon`允许接口匿名访问。JwtInterceptor在Spring MVC中拦截请求,检查JWT令牌有效性。InterceptorConfig配置拦截器,注册并设定拦截与排除规则。UserController示例展示了注册、登录(允许匿名)和需要验证的用户详情接口。
420 1
|
2月前
|
安全 算法 Java
SpringBoot+JWT+Shiro+MybatisPlus实现Restful快速开发后端脚手架
SpringBoot+JWT+Shiro+MybatisPlus实现Restful快速开发后端脚手架
156 0
|
2月前
|
开发框架 安全 Java
【Java专题_01】springboot+Shiro+Jwt整合方案
【Java专题_01】springboot+Shiro+Jwt整合方案
82 0
|
2月前
|
存储 Java 数据库
SpringBoot+JWT+Shiro
SpringBoot+JWT+Shiro
41 0
|
2月前
|
Java
Springboot整合之Shiro和JWT技术实现无感刷新9
Springboot整合之Shiro和JWT技术实现无感刷新9
|
2月前
|
JSON 安全 Java
Springboot整合之Shiro和JWT技术实现无感刷新8
Springboot整合之Shiro和JWT技术实现无感刷新8
|
2月前
|
存储 前端开发 NoSQL
Springboot整合之Shiro和JWT技术实现无感刷新7
Springboot整合之Shiro和JWT技术实现无感刷新7
|
2月前
|
缓存 NoSQL Java
Springboot整合之Shiro和JWT技术实现无感刷新6
Springboot整合之Shiro和JWT技术实现无感刷新6