需求
Spring boot 集成 shiro 、JWT
解决在token 过滤器注入reids实例和其它spring 容器管理的实例为java.lang.NullPointerException 提示空指针bug
基本思想思路
我们可以在ShiroConfig 配置类使用@Bean把token过滤器注入到Spring 容器。
我们通常在配置shiroFilterFactoryBean 的时候使用的是
filtersMap.put("token", new CustomAuthFilter());
这样是的方式是不能使用到Spring 容器内的实例
我们需要修改shiroFilterFactoryBean 配置
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager,FilterRegistrationBean tokenRegistrationBean){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
//自定义拦截器限制并发人数,参考博客:
LinkedHashMap<String, Filter> filtersMap = new LinkedHashMap<>();
filtersMap.put("tokenFilter",tokenRegistrationBean.getFilter());
shiroFilterFactoryBean.setFilters(filtersMap);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
//配置拦截策略
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
编码实现
自定义token过滤器
/**
* @ClassName: CustomAccessControlFilter
* TODO:类文件简单描述
* @Author: 小霍
* @UpdateUser: 小霍
* @Version: 0.0.1
*/
@Slf4j
public class CustomAuthFilter extends AccessControlFilter {
@Resource
private JwtUtil jwtUtil;
@Resource
private ObjectMapper objectMapper;
@Resource
private RedisService redisService;
/**
* 如果返回的是true 就流转到下一个链式调用
* 返回false 就会流转到 onAccessDenied方法
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param servletRequest
• * @param servletResponse
• * @param o
* @return boolean
* @throws
*/
@Override
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
return false;
}
/**
* 如果返true 就会流转到下一个链式调用
* false 就是不会流转到下一个链式调用
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param servletRequest
• * @param servletResponse
* @return boolean
* @throws
*/
@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) {
HttpServletRequest request= (HttpServletRequest) servletRequest;
String accessToken=request.getHeader(Constant.ACCESS_TOKEN);
log.info("url:{}",request.getRequestURI());
try {
if(!StringUtils.hasLength(accessToken)){
throw new BusinessException(ResponseCode.TOKEN_ERROR.getCode(),ResponseCode.TOKEN_ERROR.getMessage());
}
if(!jwtUtil.validateToken(accessToken)){
throw new BusinessException(ResponseCode.TOKEN_EXP.getCode(),ResponseCode.TOKEN_EXP.getMessage());
}
Long userId=jwtUtil.getUserId(accessToken);
if(redisService.validateUser(userId)){
throw new BusinessException(ResponseCode.FORCE_QUIT_USER);
}
JwtAuthToken jwtAuthToken=new JwtAuthToken(accessToken,userId);
getSubject(servletRequest,servletResponse).login(jwtAuthToken);
}catch (BusinessException e){
customResponse(servletResponse,e.getCode(),e.getMessage());
return false;
} catch (AuthenticationException e) {
if(e.getCause() instanceof BusinessException){
BusinessException exception= (BusinessException) e.getCause();
customResponse(servletResponse,exception.getCode(),exception.getMessage());
}else {
customResponse(servletResponse, ResponseCode.TOKEN_ERROR.getCode(), ResponseCode.TOKEN_ERROR.getMessage());
}
return false;
}catch (Exception e){
customResponse(servletResponse, ResponseCode.SYSTEM_ERROR.getCode(), ResponseCode.SYSTEM_ERROR.getMessage());
return false;
}
return true;
}
/**
* 自定义响应前端
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param response
• * @param code
• * @param msg
* @return void
* @throws
*/
private void customResponse(ServletResponse response,int code ,String msg){
DataResult result= DataResult.fail(code,msg);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
OutputStream outputStream=null;
try {
outputStream= response.getOutputStream();
outputStream.write(objectMapper.writeValueAsString(result).getBytes("UTF-8"));
outputStream.flush();
} catch (IOException e) {
log.error("customResponse...error:{}",e);
}finally {
if(outputStream!=null){
try {
outputStream.close();
} catch (IOException e) {
log.error("IOException:{}",e);
}
}
}
}
}
自定义Ream
public class CustomRealm extends AuthorizingRealm {
@Value("shiro.super.account")
private List<String> superAccounts;
@Override
protected boolean isPermitted(Permission permission, AuthorizationInfo info) {
/**
* 系统内部超级管理不需要去校验权限
*/
if(superAccounts.stream().anyMatch(account-> account.equalsIgnoreCase((String) SecurityUtils.getSubject().getPrincipal()))){
return true;
}
return super.isPermitted(permission, info);
}
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JwtAuthToken;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(token.getPrincipal(),token.getCredentials(),CustomRealm.class.getName());
return info;
}
}
自定义认证器默认通过校验
public class CustomCredentialsMatcher extends HashedCredentialsMatcher {
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
return true;
}
}
禁用shiro session
public class CustomWebSubjectFactory extends DefaultWebSubjectFactory {
@Override
public Subject createSubject(SubjectContext context) {
// 禁用session
context.setSessionCreationEnabled(false);
return super.createSubject(context);
}
}
封装shiro AuthenticationToken
public class JwtAuthToken implements AuthenticationToken {
private String token;
private Long userId;
public JwtAuthToken(String token,Long userId) {
this.token = token;
this.userId=userId;
}
@Override
public Object getPrincipal() {
return userId;
}
@Override
public Object getCredentials() {
return token;
}
}
定义shiro 配置
@Configuration
public class ShiroConfig {
@Bean
public DefaultWebSubjectFactory subjectFactory() {
return new CustomWebSubjectFactory();
}
@Bean
public CustomAuthFilter customAuthFilter(){
return new CustomAuthFilter();
}
@Bean
public CustomCredentialsMatcher customCredentialsMatcher(){
return new CustomCredentialsMatcher();
}
@Bean
public CustomRealm customRealm(CustomCredentialsMatcher customCredentialsMatcher){
CustomRealm realm=new CustomRealm();
realm.setCredentialsMatcher(customCredentialsMatcher);
return realm;
}
@Bean
public DefaultWebSecurityManager securityManager(CustomRealm customRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(customRealm);
//关闭shiro session 功能
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
securityManager.setSubjectDAO(subjectDAO);
return securityManager;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager,FilterRegistrationBean tokenRegistrationBean){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
LinkedHashMap<String, Filter> filtersMap = new LinkedHashMap<>();
filtersMap.put("tokenFilter",tokenRegistrationBean.getFilter());
shiroFilterFactoryBean.setFilters(filtersMap);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
//配置拦截策略
filterChainDefinitionMap.put("/api/v1/auth/user/login", "anon");
filterChainDefinitionMap.put("/**","tokenFilter");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public FilterRegistrationBean tokenRegistrationBean(CustomAuthFilter customAuthFilter) {
FilterRegistrationBean registration = new FilterRegistrationBean(customAuthFilter);
registration.setEnabled(false);
return registration;
}