【shiro】AccessControlFilter

6、AccessControlFilter

AccessControlFilter提供了访问控制的基础功能;比如是否允许访问/当访问拒绝时如何处理等:

Java代码  
  1. abstract boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception;  
  2. boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception;  
  3. abstract boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception;   

isAccessAllowed:表示是否允许访问;mappedValue就是[urls]配置中拦截器参数部分,如果允许访问返回true,否则false;

onAccessDenied:表示当访问拒绝时是否已经处理了;如果返回true表示需要继续处理;如果返回false表示该拦截器实例已经处理了,将直接返回即可。

 

onPreHandle会自动调用这两个方法决定是否继续处理:

Java代码  
  1. boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {  
  2.     return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);  
  3. }   

onAccessDenied 会调用(参考 http://www.cnblogs.com/CESC4/p/7597563.html

isAccessAllowed和onAccessDenied是AccessControlFilter的方法
【-A-】调用 FormAuthenticationFilter onAccessDenied 方法
return executeLogin(request, response);

 

另外AccessControlFilter还提供了如下方法用于处理如登录成功后/重定向到上一个请求: 

Java代码  
  1. void setLoginUrl(String loginUrl) //身份验证时使用,默认/login.jsp  
  2. String getLoginUrl()  
  3. Subject getSubject(ServletRequest request, ServletResponse response) //获取Subject实例  
  4. boolean isLoginRequest(ServletRequest request, ServletResponse response)//当前请求是否是登录请求  
  5. void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException //将当前请求保存起来并重定向到登录页面  
  6. void saveRequest(ServletRequest request) //将请求保存起来,如登录成功后再重定向回该请求  
  7. void redirectToLogin(ServletRequest request, ServletResponse response) //重定向到登录页面   

比如基于表单的身份验证就需要使用这些功能。

 

到此基本的拦截器就完事了,如果我们想进行访问访问的控制就可以继承AccessControlFilter;如果我们要添加一些通用数据我们可以直接继承PathMatchingFilter。


4、扩展AccessControlFilter

AccessControlFilter继承了PathMatchingFilter,并扩展了了两个方法:

Java代码  
  1. public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {  
  2.     return isAccessAllowed(request, response, mappedValue)  
  3.      || onAccessDenied(request, response, mappedValue);  
  4. }   

isAccessAllowed:即是否允许访问,返回true表示允许;

onAccessDenied:表示访问拒绝时是否自己处理,如果返回true表示自己不处理且继续拦截器链执行,返回false表示自己已经处理了(比如重定向到另一个页面)。

   

Java代码  
  1. public class MyAccessControlFilter extends AccessControlFilter {  
  2.     protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {  
  3.         System.out.println("access allowed");  
  4.         return true;  
  5.     }  
  6.     protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {  
  7.         System.out.println("访问拒绝也不自己处理,继续拦截器链的执行");  
  8.         return true;  
  9.     }  
  10. }   

然后在shiro.ini中进行如下配置:

Java代码  
  1. [filters]  
  2. myFilter4=com.github.zhangkaitao.shiro.chapter8.web.filter.MyAccessControlFilter  
  3. [urls]  
  4. /**=myFilter4  

转载于:https://www.cnblogs.com/CESC4/p/7599927.html

<think>根据用户要求,不使用AccessControlFilter,那么我们可以选择继承AuthenticatingFilter。但是之前遇到final方法的问题,我们需要避免覆盖init方法。这里我们使用另一种方式:通过覆盖onExecute方法或者在构造器中初始化,但更推荐使用Shiro的生命周期方法onFilterConfigSet。 然而,用户要求不使用AccessControlFilter,所以我们回到AuthenticatingFilter。注意:AuthenticatingFilter的init方法也是final的,所以同样不能覆盖。因此,我们采用相同的方式:覆盖onFilterConfigSet方法。 下面我们使用AuthenticatingFilter来实现OAuth2Filter,并解决初始化问题。 ```java import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.web.filter.authc.AuthenticatingFilter; import org.apache.shiro.web.util.WebUtils; import org.springframework.web.context.support.WebApplicationContextUtils; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.FilterConfig; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class OAuth2Filter extends AuthenticatingFilter { private ShiroService shiroService; private JwtUtils jwtUtils; // 覆盖onFilterConfigSet方法进行初始化 @Override protected void onFilterConfigSet(FilterConfig filterConfig) throws Exception { super.onFilterConfigSet(filterConfig); initBeans(filterConfig); } private void initBeans(FilterConfig filterConfig) { ServletContext servletContext = filterConfig.getServletContext(); this.shiroService = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext).getBean(ShiroService.class); this.jwtUtils = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext).getBean(JwtUtils.class); } // 创建自定义的Token @Override protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception { HttpServletRequest httpRequest = (HttpServletRequest) request; String token = getRequestToken(httpRequest); if (token == null) { return null; } return new OAuth2Token(token); } // 从请求中获取token private String getRequestToken(HttpServletRequest httpRequest) { String token = httpRequest.getHeader("Authorization"); if (token != null && token.startsWith("Bearer ")) { return token.substring(7); } return null; } // 是否允许访问 @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { // 这里可以添加一些允许访问的条件,例如OPTIONS请求放行 if (((HttpServletRequest) request).getMethod().equalsIgnoreCase("OPTIONS")) { return true; } return false; } // 当访问被拒绝时,即需要认证 @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { // 尝试认证 return executeLogin(request, response); } // 认证成功 @Override protected boolean onLoginSuccess(AuthenticationToken token, ServletRequest request, ServletResponse response) throws Exception { return true; // 继续执行请求 } // 认证失败 @Override protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) { HttpServletResponse httpResponse = (HttpServletResponse) response; httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); try { httpResponse.getWriter().write("{\"code\":401,\"msg\":\"Unauthorized\"}"); } catch (Exception ignored) {} return false; } } ``` ### 代码说明: 1. **继承AuthenticatingFilter**: - 使用Shiro的`AuthenticatingFilter`作为基类,它提供了认证流程的框架。 - 需要实现`createToken`方法来创建自定义的认证Token。 2. **初始化Bean**: - 在`onFilterConfigSet`方法中初始化`shiroService`和`jwtUtils`,这个方法在Filter初始化时被调用,且不是final方法。 3. **自定义Token**: - 创建了一个`OAuth2Token`类(需要自定义)来封装JWT令牌,实现`AuthenticationToken`接口。 ```java public class OAuth2Token implements AuthenticationToken { private String token; public OAuth2Token(String token) { this.token = token; } @Override public Object getPrincipal() { return token; } // 通常返回用户名,但这里返回token,在Realm中解析 @Override public Object getCredentials() { return token; } } ``` 4. **认证流程**: - `isAccessAllowed`:默认返回false,表示需要认证。这里放行了OPTIONS请求(用于跨域)。 - `onAccessDenied`:调用`executeLogin`方法,触发`createToken`和登录流程。 - `onLoginSuccess`:认证成功,继续执行请求。 - `onLoginFailure`:认证失败,返回401错误。 5. **Realm中的认证**: - 需要自定义一个Realm来验证`OAuth2Token`。 - 在Realm的`doGetAuthenticationInfo`方法中,通过`jwtUtils`验证Token,并返回用户信息。 ### 自定义Realm示例: ```java public class OAuth2Realm extends AuthorizingRealm { private JwtUtils jwtUtils; // 设置支持的Token类型 @Override public boolean supports(AuthenticationToken token) { return token instanceof OAuth2Token; } // 认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String jwtToken = (String) token.getCredentials(); if (!jwtUtils.validateToken(jwtToken)) { throw new AuthenticationException("Invalid token"); } String username = jwtUtils.extractUsername(jwtToken); // 这里可以查询数据库,或者根据token中的信息构建用户 return new SimpleAuthenticationInfo(username, jwtToken, getName()); } // 授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { // 授权逻辑... return null; } } ``` ### 配置建议: 在Spring Boot中配置Shiro: ```java @Configuration public class ShiroConfig { @Bean public Realm realm() { return new OAuth2Realm(); } @Bean public DefaultWebSecurityManager securityManager(Realm realm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(realm); return securityManager; } @Bean public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager, OAuth2Filter oauth2Filter) { ShiroFilterFactoryBean factory = new ShiroFilterFactoryBean(); factory.setSecurityManager(securityManager); Map<String, Filter> filters = new HashMap<>(); filters.put("oauth2", oauth2Filter); factory.setFilters(filters); Map<String, String> filterMap = new LinkedHashMap<>(); filterMap.put("/api/**", "oauth2"); factory.setFilterChainDefinitionMap(filterMap); return factory; } @Bean public OAuth2Filter oauth2Filter() { return new OAuth2Filter(); } } ``` 注意:这里需要将`JwtUtils`和`ShiroService`注入到`OAuth2Filter`和`OAuth2Realm`中,可以通过Spring的依赖注入完成。 §§相关问题§§: 1. 在Shiro中,AuthenticatingFilter和AuthorizationFilter的区别是什么? 2. 如何实现JWT令牌在Shiro中的自动刷新? 3. 在微服务架构中,如何统一处理JWT令牌的认证和授权? 4. Shiro的Realm如何实现动态权限加载?</think>根据您的要求,我将提供一个不使用 `AccessControlFilter` 的解决方案。我们将使用 Shiro 的 `AuthenticatingFilter` 并解决 `init()` 方法 final 的问题。以下是完整的实现方案: ```java import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.web.filter.authc.AuthenticatingFilter; import org.apache.shiro.web.util.WebUtils; import org.springframework.context.ApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; import javax.servlet.FilterConfig; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class OAuth2Filter extends AuthenticatingFilter { private ShiroService shiroService; private JwtUtils jwtUtils; // 使用Shiro提供的初始化钩子 @Override protected void onFilterConfigSet(FilterConfig filterConfig) throws Exception { super.onFilterConfigSet(filterConfig); initBeans(filterConfig); } private void initBeans(FilterConfig filterConfig) { ApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext( filterConfig.getServletContext() ); this.shiroService = ctx.getBean(ShiroService.class); this.jwtUtils = ctx.getBean(JwtUtils.class); } // 创建自定义认证令牌 @Override protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) { HttpServletRequest httpRequest = WebUtils.toHttp(request); String token = extractJwtToken(httpRequest); return new JwtAuthenticationToken(token); } // 从请求中提取JWT令牌 private String extractJwtToken(HttpServletRequest request) { String authHeader = request.getHeader("Authorization"); if (authHeader != null && authHeader.startsWith("Bearer ")) { return authHeader.substring(7); } return null; } // 访问控制逻辑 @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { // 允许OPTIONS请求(预检请求) if (WebUtils.toHttp(request).getMethod().equalsIgnoreCase("OPTIONS")) { return true; } return false; // 其他请求需要认证 } // 当需要认证时的处理 @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { // 尝试执行认证 return executeLogin(request, response); } // 认证成功处理 @Override protected boolean onLoginSuccess(AuthenticationToken token, ServletRequest request, ServletResponse response) throws Exception { // 可在此处添加额外的权限检查 return true; // 继续请求处理 } // 认证失败处理 @Override protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException ae, ServletRequest request, ServletResponse response) { HttpServletResponse httpResponse = WebUtils.toHttp(response); httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); httpResponse.setContentType("application/json"); try { httpResponse.getWriter().write("{\"error\":\"Unauthorized\",\"message\":\"" + ae.getMessage() + "\"}"); } catch (Exception e) { // 日志记录 } return false; // 停止请求处理 } } // 自定义JWT认证令牌 class JwtAuthenticationToken implements AuthenticationToken { private final String token; public JwtAuthenticationToken(String token) { this.token = token; } @Override public Object getPrincipal() { return token; // 通常返回用户名,但这里返回令牌用于验证 } @Override public Object getCredentials() { return token; } } ``` ### 配套的Realm实现: ```java import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; public class JwtRealm extends AuthorizingRealm { private final JwtUtils jwtUtils; private final ShiroService shiroService; public JwtRealm(JwtUtils jwtUtils, ShiroService shiroService) { this.jwtUtils = jwtUtils; this.shiroService = shiroService; setAuthenticationTokenClass(JwtAuthenticationToken.class); } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { JwtAuthenticationToken jwtToken = (JwtAuthenticationToken) token; String tokenString = (String) jwtToken.getCredentials(); // 验证JWT令牌 if (!jwtUtils.validateToken(tokenString)) { throw new AuthenticationException("Invalid JWT token"); } // 提取用户名并创建认证信息 String username = jwtUtils.extractUsername(tokenString); return new SimpleAuthenticationInfo(username, tokenString, getName()); } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = (String) principals.getPrimaryPrincipal(); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); // 从服务加载用户角色和权限 authorizationInfo.setRoles(shiroService.getUserRoles(username)); authorizationInfo.setStringPermissions(shiroService.getUserPermissions(username)); return authorizationInfo; } } ``` ### Spring Boot配置示例: ```java @Configuration public class ShiroConfig { @Bean public Realm jwtRealm(JwtUtils jwtUtils, ShiroService shiroService) { return new JwtRealm(jwtUtils, shiroService); } @Bean public DefaultWebSecurityManager securityManager(Realm realm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(realm); return securityManager; } @Bean public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean factory = new ShiroFilterFactoryBean(); factory.setSecurityManager(securityManager); // 配置自定义过滤器 Map<String, Filter> filters = new HashMap<>(); filters.put("oauth2", new OAuth2Filter()); factory.setFilters(filters); // 配置URL保护规则 Map<String, String> filterChainMap = new LinkedHashMap<>(); filterChainMap.put("/api/**", "oauth2"); // 需要认证 filterChainMap.put("/public/**", "anon"); // 公开访问 factory.setFilterChainDefinitionMap(filterChainMap); return factory; } } ``` ### 关键实现说明: 1. **解决final init()问题**: - 使用 `onFilterConfigSet()` 方法作为初始化钩子 - 在方法内部获取Spring上下文并初始化Bean 2. **认证流程**: - `createToken()`:从请求中提取JWT并创建认证令牌 - `executeLogin()`:触发Shiro的认证流程 - `onLoginSuccess()`:认证成功后的处理 - `onLoginFailure()`:认证失败返回401响应 3. **自定义JwtRealm**: - 实现令牌验证逻辑 - 加载用户角色和权限 - 与ShiroService集成获取权限数据 4. **跨域支持**: - 在 `isAccessAllowed()` 中放行OPTIONS预检请求 - 确保前端应用能正常进行跨域请求 5. **错误处理**: - 返回标准化的JSON错误响应 - 设置正确的HTTP状态码 ### 最佳实践建议: 1. **令牌刷新机制**: ```java // 在onLoginSuccess中添加 protected boolean onLoginSuccess(AuthenticationToken token, ...) { String jwtToken = (String) token.getCredentials(); if (jwtUtils.isTokenExpiringSoon(jwtToken)) { String newToken = jwtUtils.refreshToken(jwtToken); WebUtils.toHttp(response).setHeader("X-New-Token", newToken); } return true; } ``` 2. **动态权限检查**: ```java // 在Realm的授权方法中 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = (String) principals.getPrimaryPrincipal(); String currentPath = WebUtils.getRequestUri(WebUtils.getHttpRequest(SecurityUtils.getSubject())); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.setRoles(shiroService.getRolesForPath(username, currentPath)); info.setStringPermissions(shiroService.getPermissionsForPath(username, currentPath)); return info; } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值