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 ""; }
相关类的解释说明;
- JwtToken:Token 的对象信息,你可以设置用户ID、用户密码
- JwtRealm:一个自定义的验证服务,需要继承 AuthorizingRealm 类。
- JwtFilter:自定义的 Filter 过滤器。
- JwtUtil:token的创建、解析、验证工具类。
- ShiroConfig:Shiro 的一个配置启动类。
- ApiAccessController:新增加的API访问准入管理;当访问 OpenAI 接口时,需要进行准入验证。