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 接口时,需要进行准入验证。
  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值