一、集成场景说明
- Shiro 负责权限控制和会话管理
- OAuth2 提供身份认证(如通过第三方授权服务器获取 access_token)
- 前端携带 access_token 访问后端,后端用 Shiro 校验 token 并进行授权
二、集成思路
- 自定义 OAuth2 认证过滤器:拦截请求,校验 access_token
- 自定义 Realm:根据 access_token 获取用户信息和权限
- 配置 Shiro Filter Chain:指定哪些接口需要 OAuth2 认证
三、关键实现步骤
1. 添加依赖
<!-- Shiro 依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>最新版本</version>
</dependency>
<!-- 可选:OAuth2/JWT 相关依赖(如 jjwt 或 jose4j) -->
2. 自定义 OAuth2Token
public class OAuth2Token implements AuthenticationToken {
private String token;
public OAuth2Token(String token) {
this.token = token;
}
@Override
public Object getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return token;
}
}
3. 自定义 OAuth2Filter
public class OAuth2Filter extends AuthenticatingFilter {
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
String token = getAccessToken(request);
return token == null ? null : new OAuth2Token(token);
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
// 不允许匿名访问
return false;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
String token = getAccessToken(request);
if (token == null) {
// 返回401
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
// 提交给Realm进行认证
return executeLogin(request, response);
}
private String getAccessToken(ServletRequest request) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String token = httpRequest.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
token = token.substring(7);
}
return token;
}
}
4. 自定义 OAuth2Realm
public class OAuth2Realm extends AuthorizingRealm {
// 必须支持OAuth2Token
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof OAuth2Token;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String accessToken = (String) token.getCredentials();
// 1. 校验token有效性(如解析JWT,或请求OAuth2服务器)
// 2. 获取用户信息
String userId = parseUserIdFromToken(accessToken); // 自定义实现
if (userId == null) {
throw new AuthenticationException("Token无效或已过期");
}
return new SimpleAuthenticationInfo(userId, accessToken, getName());
}
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String userId = (String) principals.getPrimaryPrincipal();
// 1. 根据userId查角色和权限
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addRole("user");
info.addStringPermission("data:read");
return info;
}
}
5. Shiro 配置集成 Filter
@Configuration
public class ShiroConfig {
@Bean
public OAuth2Realm oAuth2Realm() {
return new OAuth2Realm();
}
@Bean
public SecurityManager securityManager(OAuth2Realm realm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
return securityManager;
}
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
factoryBean.setSecurityManager(securityManager);
Map<String, Filter> filters = new HashMap<>();
filters.put("oauth2", new OAuth2Filter());
factoryBean.setFilters(filters);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/api/login", "anon");
filterChainDefinitionMap.put("/api/**", "oauth2"); // 所有API接口必须携带token
factoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return factoryBean;
}
}
四、集成 JWT(可选)
如果 access_token 是 JWT,可以用 jjwt 或 jose4j 进行校验和解析:
public String parseUserIdFromToken(String token) {
Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
return claims.getSubject();
}
五、常见问题与建议
- token 校验失败:确保解析逻辑和密钥正确。
- 权限控制:在 doGetAuthorizationInfo 中根据实际业务查权限。
- 前端必须携带 Authorization: Bearer xxxxx 头部
- OAuth2 服务器集成:如需和第三方认证服务器对接,需实现 token 校验逻辑(如远程校验)。
六、进阶实践
1. 多端支持(移动端、前端 SPA)
- 移动端/SPA 前端:通常在登录后获取 access_token,后续所有请求都在 HTTP Header(如
Authorization: Bearer xxxxx)中携带 token。 - Shiro 后端:所有需要保护的接口都用自定义的 OAuth2Filter 拦截,拒绝无 token 或 token 无效的请求。
注意事项:
- 前端需统一处理 401 响应,做跳转或弹窗提示。
- 后端可根据 token 解析出的用户信息,做个性化响应。
2. Token 刷新机制
在 OAuth2 中,access_token 通常有较短有效期,refresh_token 用于换取新 token。
实现思路:
- 前端在 access_token 过期后,自动携带 refresh_token 请求后端刷新接口
/api/token/refresh。 - 后端可实现
/api/token/refresh,校验 refresh_token,有效则生成新 access_token 返回。 - Shiro 的 OAuth2Filter 只负责校验 access_token,刷新逻辑可独立实现。
接口示例:
@PostMapping("/api/token/refresh")
public ResponseEntity<?> refresh(@RequestBody RefreshTokenDTO dto) {
// 校验 refresh_token,生成新 access_token
String newToken = oauth2Service.refreshToken(dto.getRefreshToken());
return ResponseEntity.ok(newToken);
}
3. Shiro 权限注解支持
Shiro 支持方法级权限控制,可以结合 OAuth2 实现细粒度授权。
注解示例:
@RequiresAuthentication
@RequiresRoles("admin")
@RequiresPermissions("data:write")
public void updateData() { ... }
Spring Boot 集成需开启注解支持:
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
4. 分布式部署场景
- Token 校验建议:
- 采用 JWT(自包含,无需远程校验),每个服务只需持有公钥/密钥即可校验。
- 如果使用 opaque token(不透明 token),需每次向 OAuth2 认证服务器校验,可能影响性能。
- 会话管理建议:
- 不建议用 Shiro 的 session,建议无状态(stateless)设计,所有认证信息都在 token 内。
5. 异常处理与安全建议
- 统一异常处理:如 token 失效、无权限,返回标准 401/403 响应,前端统一处理。
- 安全建议:
- access_token 不要存储在本地不安全位置(如 localStorage),推荐 HttpOnly Cookie 或安全存储。
- refresh_token 只用于换取新 token,不用于业务接口认证。
- token 过期时间设置合理,防止被长期滥用。
- 对高危接口增加额外校验,如验证码、二次认证。
6. 对接第三方认证服务(如 Authing、Keycloak、微信、阿里云)
通用流程:
- 前端通过第三方认证服务登录,获取 access_token。
- 后端 OAuth2Realm 在 doGetAuthenticationInfo 中调用第三方接口校验 token 或解析 JWT。
- 获取用户信息,完成 Shiro 的 Subject 认证和授权。
Keycloak JWT 校验示例:
public String parseUserIdFromToken(String token) {
// 使用 Keycloak 公钥解析 JWT
Claims claims = Jwts.parser().setSigningKey(keycloakPublicKey).parseClaimsJws(token).getBody();
return claims.getSubject();
}
微信/阿里云等:一般需要远程请求第三方接口校验 token,获取用户信息。
7. 代码结构建议
OAuth2Token、OAuth2Filter、OAuth2Realm独立包管理,便于维护。- 所有权限控制、认证逻辑都集中在 Realm 和 Filter,业务代码只关心授权注解。
- 刷新 token、异常处理等可放在公共服务层。
七、常见问题排查
| 问题 | 排查建议 |
|---|---|
| token 校验失败 | 校验算法/密钥/第三方接口是否正确,token 是否过期 |
| 权限注解不生效 | 检查 Advisor 配置是否开启,注解是否正确使用 |
| 401/403 响应前端未处理 | 前端需拦截响应码,做统一跳转/弹窗 |
| 分布式部署 session 丢失 | 推荐无状态,避免依赖 Shiro session |
| refresh_token 被盗用 | 设置合理有效期,绑定设备/IP,定期轮换 |
创作不易,点赞关注,互通有无!


2320

被折叠的 条评论
为什么被折叠?



