默认的拦截器
下面的代码为请求声明其拦截器的类型
filterChainDefinitionMap.put("/login/index","anon");
filterChainDefinitionMap.put("/**","authc");
整合了shiro安全框架后,当运行一个Web应用程序时,Shiro将会创建一些有用的默认 Filter 实例,并自动地将它们置为可用,而这些默认的 Filter 实例是被 DefaultFilter 枚举类定义的,当然我们也可以自定义 Filter 实例
常用的主要就是 anon,authc,user,roles,perms 等
注意:anon, authc, authcBasic, user 是第一组认证过滤器,perms, port, rest, roles, ssl 是第二组授权过滤器,要通过授权过滤器,就先要完成登陆认证操作(即先要完成认证才能前去寻找授权) 才能走第二组授权器(例如访问需要 roles 权限的 url,如果还没有登陆的话,会直接跳转到 shiroFilterFactoryBean.setLoginUrl(); 设置的 url
自定义JwtFilter
下面是采用自定义的拦截器去完成token校验,首先定义一个JwtFilter类继承提供的父拦截器,并重写其中的方法
package com.ojj.config;
import com.ojj.config.vo.JwtToken;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author ojj
* @title: JwtFilter
* @projectName test
* @description: 拦截器
* @date 2022/1/6 14:57
*/
public class JwtFilter extends BasicHttpAuthenticationFilter {
/**
* 拦截器的前置 最先执行的 这里只做了一个跨域设置
*/
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
System.out.println("JwtFilter -----> preHandle() 方法执行");
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
res.setHeader("Access-control-Allow-Origin", req.getHeader("Origin"));
res.setHeader("Access-control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
res.setHeader("Access-control-Allow-Headers", req.getHeader("Access-Control-Request-Headers"));
// 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
if (req.getMethod().equals(RequestMethod.OPTIONS.name())) {
res.setStatus(HttpStatus.OK.value());
return false;
}
return super.preHandle(request, response);
}
/**
* preHandle 执行完之后会执行这个方法
* 再这个方法中 我们根据条件判断去去执行isLoginAttempt和executeLogin方法
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
System.out.println("JwtFilter -----> isAccessAllowed() 方法执行");
/**
* 先去调用 isLoginAttempt方法 字面意思就是是否尝试登陆 如果为true
* 执行executeLogin方法
*/
if (isLoginAttempt(request, response)) {
executeLogin(request, response);
return true;
}
return false;
}
/**
* 这里我们只是简单去做一个判断请求头中的token信息是否为空
* 如果没有我们想要的请求头信息则直接返回false
*/
@Override
protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
System.out.println("JwtFilter -----> isLoginAttempt() 方法执行");
HttpServletRequest req = (HttpServletRequest) request;
String token = req.getHeader("X-Access-Token:");
return token != null;
}
/**
* 执行登陆
* 因为已经判断token不为空了,所以直接执行登陆逻辑
* 讲token放入JwtToken类中去
* 然后getSubject方法是调用到了MyRealm的 执行方法 因为上面我是抛错的所有最后做个异常捕获就好了
*/
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) {
System.out.println("JwtFilter -----> executeLogin() 方法执行");
HttpServletRequest req = (HttpServletRequest) request;
String token = req.getHeader("X-Access-Token:");
JwtToken jwtToken = new JwtToken(token);
//然后交给自定义的realm对象去登陆, 如果错误他会抛出异常并且捕获
System.out.println("-----执行登陆开始-----");
getSubject(request, response).login(jwtToken);
System.out.println("-----执行登陆结束----- 未抛出异常");
return true;
}
}
改造shiroConfig
import javax.servlet.Filter;
@Bean()
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
Map<String, Filter> filterMap = new HashMap<>();
filterChainDefinitionMap.put("/login/loginInfo","anon");
filterChainDefinitionMap.put("/login/index","anon");
//引入自定义拦截器,命名为jwt
filterMap.put("jwt",new JwtFilter());
//放到shiro工厂中
shiroFilterFactoryBean.setFilters(filterMap);
//其余接口一律拦截
//主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截
filterChainDefinitionMap.put("/**","jwt");//替换默认的拦截器为自定义的jwt
// filterChainDefinitionMap.put("/**","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
shiroFilterFactoryBean.setLoginUrl("/login/tologin");
return shiroFilterFactoryBean;
}
此时本以为运行会成功,居然出错了,报错的关键的内容如下:
Realm does not support authentication token
大概是因为每一个Ream都有一个supports方法,用于检测是否支持此Token, 而在该函数中,默认的采用了return false;
所以在自己的Realm中重写方法,JwtToken是用来传递校验信息的,这里用了自定义类型。
/**
* 必须重写此方法,不然Shiro会报错
*/
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof UsernamePasswordToken;
}
而之前是用
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
subject.login(token);
现在访问需要权限的接口,就会被JwtFilter拦截,然后判断是否有token,再然后可以加入token校验,如果没有token或者不符合权限就会被拦截。
这里由于我写的页面没带token,不太懂把token放到请求头信息中的X-Access-Token:。所以意会一下这里
所以登录成功后点击add/update 还是不会成功,由于上面的原因。
上面是shiro 对于spring security类似 直接看过滤器代码
package com.xii.security.filter;
import com.xii.commonutils.R;
import com.xii.commonutils.ResponseUtil;
import com.xii.security.security.TokenManager;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.StringUtils;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @description: 访问过滤器
*
* @return {@link null}
* @author wangxihao
* @email [email protected]
**/
public class TokenAuthenticationFilter extends BasicAuthenticationFilter {
private TokenManager tokenManager;
private RedisTemplate redisTemplate;
public TokenAuthenticationFilter(AuthenticationManager authManager, TokenManager tokenManager,RedisTemplate redisTemplate) {
super(authManager);
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
}
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
throws IOException, ServletException {
logger.info("================="+req.getRequestURI());
if(req.getRequestURI().indexOf("admin") == -1) {
chain.doFilter(req, res);
return;
}
UsernamePasswordAuthenticationToken authentication = null;
try {
authentication = getAuthentication(req);
} catch (Exception e) {
ResponseUtil.out(res, R.error());
}
if (authentication != null) {
SecurityContextHolder.getContext().setAuthentication(authentication);
} else {
ResponseUtil.out(res, R.error());
}
chain.doFilter(req, res);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
// token置于header里
String token = request.getHeader("token");
if (token != null && !"".equals(token.trim())) {
String userName = tokenManager.getUserFromToken(token);
List<String> permissionValueList = (List<String>) redisTemplate.opsForValue().get(userName);
Collection<GrantedAuthority> authorities = new ArrayList<>();
for(String permissionValue : permissionValueList) {
if(StringUtils.isEmpty(permissionValue)) continue;
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
authorities.add(authority);
}
if (!StringUtils.isEmpty(userName)) {
return new UsernamePasswordAuthenticationToken(userName, token, authorities);
}
return null;
}
return null;
}
}