一、添加依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.10.0</version>
</dependency>
二、shiro各对象层级关系
三、自定义TokenFilter
package com.example.jiakao.common.shiro;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.credential.CredentialsMatcher;
/**
* 判断密码=>判断token
*/
public class TokenMatcher implements CredentialsMatcher {
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
return false;
}
}
package com.example.jiakao.common.shiro;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
public class TokenRealm extends AuthorizingRealm{
public TokenRealm(TokenMatcher matcher){
super(matcher);
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
return null;
}
}
package com.example.jiakao.common.shiro;
import com.example.jiakao.common.constant.ResultCode;
import com.example.jiakao.common.ehcache.Caches;
import com.example.jiakao.exception.http.ForbiddenException;
import com.example.jiakao.pojo.vo.list.UserInfoVo;
import org.apache.shiro.web.filter.AccessControlFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
/**
* 验证用户的合法性,是否有相关权限
*/
public class TokenFilter extends AccessControlFilter {
public static final String HEADER_TOKEN = "Token";
/**
* 当请求被TokenFilter拦截时,就会调用这个方法
* 可以在此方法中做初步判断
* @param servletRequest
* @param servletResponse
* @param o
* @return 返回True:允许访问,可以进入下一个链条调用;返回False:不允许访问,不会进入下一个链条调用,进入onAccessDenied方法
* @throws Exception
*/
@Override
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
// HttpServletRequest request = (HttpServletRequest) servletRequest;
放行所有OPTIONS请求
// return "OPTIONS".equals(request.getMethod());
return false;
}
/**
* 当isAccessAllowed返回false时进入该方法
* 在这个方法中进行token校验
* @param servletRequest
* @param servletResponse
* @return 返回true:允许访问,进入下一链条调用;返回false:拒绝访问,不会进入下一链条调用
* @throws Exception
*/
@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception{
HttpServletRequest request = (HttpServletRequest) servletRequest;
String token = request.getHeader(HEADER_TOKEN);
if(token == null){
throw new ForbiddenException(ResultCode.LACK_TOKEN);
}
UserInfoVo user = Caches.getToken(token);
if(user == null){
throw new ForbiddenException(ResultCode.TOKEN_ERROR);
}
// TODO:鉴权
return true;
}
}
package com.example.jiakao.common.shiro;
import com.example.jiakao.common.filter.ErrorFilter;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroCfg {
@Bean
public Realm realm(){
return new TokenRealm(new TokenMatcher());
}
/**
* 用于告诉shiro如何进行拦截
* 1、拦截哪些url
* 2、每个url需要进行哪些filter
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(Realm realm){
// 安全管理器
DefaultWebSecurityManager mgr = new DefaultWebSecurityManager(realm);
ShiroFilterFactoryBean filterBean = new ShiroFilterFactoryBean();
filterBean.setSecurityManager(mgr);
// 设置一些自定义filter
Map<String, Filter> filters = new HashMap<>();
filters.put("token",new TokenFilter());
filterBean.setFilters(filters);
Map<String,String> urlMap = new LinkedHashMap<>();
// anon匿名访问->不需要登录就可以访问,顺序越靠前,优先级越高
urlMap.put("/user/login","anon");
urlMap.put("/user/captcha","anon");
urlMap.put("/*swagger*/**","anon");
urlMap.put("/v2/api-docs/**","anon");
urlMap.put("/webjars/**","anon");
// 全局filter的异常处理
urlMap.put(ErrorFilter.ERROR_URI,"anon");
urlMap.put("/**","token");
filterBean.setFilterChainDefinitionMap(urlMap);
return filterBean;
}
}
四、Filter阶段异常处理
Filter阶段抛出的异常默认由Tomcat处理,抛出500异常,由于未进入到Controller阶段,因此无法由Controller的全局异常处理器进行捕获处理。Spring-boot中FIlter是嵌套执行,所以在第一个Filter中可以捕获后续所有Filter抛出的异常,如果我们在此时对异常进行捕获,然后将请求转发到特定的ErrorController,进行异常抛出,此时我们就可以对Filter阶段的异常进行统一处理。
package com.example.jiakao.common.filter;
import com.example.jiakao.exception.http.HttpException;
import javax.servlet.*;
import java.io.IOException;
/**
* Filter的doFilter是嵌套执行,所以在第一个Filter中可以捕获后续所有Filter抛出的异常,
* 然后将其转发给ErrorController,由其抛出,便于全局Controller异常处理
*/
public class ErrorFilter implements Filter {
public static final String ERROR_URI = "/handleError";
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try{
chain.doFilter(request,response);
}catch (Exception e){
request.setAttribute(ERROR_URI, e);
// 转发
request.getRequestDispatcher(ERROR_URI).forward(request,response);
}
}
}
注册自定义ErrorFilter,层级放在顶层。
package com.example.jiakao.common.config;
import com.example.jiakao.common.prop.ProjProperties;
import com.example.jiakao.common.filter.ErrorFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.servlet.Filter;
@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {
@Autowired
private ProjProperties projProperties;
@Override
public void addCorsMappings(CorsRegistry registry) {
// 允许跨域访问的接口
registry.addMapping("/**")
// 允许携带cookie
.allowCredentials(true)
// 允许跨域共享的域
.allowedOrigins(projProperties.getCorsOrigins());
// 允许HTTP请求中携带哪些头部信息
// .allowedHeaders("*")
// 暴露哪些头部信息
// .exposedHeaders("*")
// .allowedMethods("GET", "POST")
}
/**
* 注册Filter
* @return
*/
@Bean
public FilterRegistrationBean<Filter> filterRegistrationBean(){
FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();
bean.setFilter(new ErrorFilter());
bean.addUrlPatterns("/*");
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
}
Shiro中Filter抛出的异常是 javax.servlet.ServletException,如果异常是由其他异常引起,放在cause中。因此我们抛出的自定义异常都在cause属性中。
package com.example.jiakao.controller;
import com.example.jiakao.common.filter.ErrorFilter;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@RestController
public class ErrorController {
@RequestMapping(ErrorFilter.ERROR_URI)
public void handle(HttpServletRequest request) throws Throwable {
Exception e = (Exception) request.getAttribute(ErrorFilter.ERROR_URI);
if(e != null){
Throwable cause = e.getCause();
if(cause != null){
throw cause;
}
throw e;
}
}
}