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 接口时,需要进行准入验证。