SpringBoot+Shiro瞎折腾——不使用Shiro的Filter模式
文章目录一、总结:Subject其实是绑定线程的二、手写实现简单验证1. Shiro配置2.使用三、再总结       想要实现基于SpringBoot+Shiro+Vue的前后端分离技术,网上教程还是不少的,在实现成功后,多问了个问题,就有了这篇文章。问题如标题
想要实现基于SpringBoot+Shiro+Vue的前后端分离技术,网上教程还是不少的,在实现成功后,多问了个问题,就有了这篇文章。问题如标题,如果不使用Shiro提供的Filter模式会怎么样:
前后端分离,如果没用RESTful架构,使用JSON rpc等类似协议,
其实是不需要Shiro基于URL路径去判断用户权限,只需要使用注解来
标识函数权限即可,如:
//统一的接口入口
@RequestMapping(value = "/api",produces = "application/json;charset=UTF-8")
@ResponseBody
public String api(HttpServletRequest request) {
//调用不同接口
}
//需要用户已登录
@RequiresAuthentication
public Result getUserInfo(JSONObject json){
//do something
}
所以,我注释掉了Shiro的Filter注册部分,然后理所当然的认为直接就可用了,也理所当然的被打脸了。原因是并没有真正了解Shiro的设计模式与其在Shiro-web中所做的事。
一、总结:Subject其实是绑定线程的
其实如果多想想,这样设计是符合Shiro定位的。Shiro本意就是不依赖任何容器与环境,可以在任意的Java环境中提供用户权限认证,如果Shiro是绑定进程的,独立App可能还没问题,因为只需要提供给一个用户使用,但服务器模式等需要为多用户提供用户鉴权的,就没办法实现了。
基于这个前提,Shiro-web的Filter,利用Filter的机制,在所有请求真实处理前,根据SessionID获取Session(SessionID获取方式可自定义,默认使用Cookie,不需要干预;也可以修改为Header、Query等等),构造一个Subject绑定到线程中,后续的权限相关处理,就都是针对这个用户的了。在该线程中,任意位置都可以直接调用:Subject subject = SecurityUtils.getSubject();
来获取用户的Subject。
Request处理完后,还需要对线程进行处理,将相关数据与线程解绑。
关键代码:
//创建Subject
//org.apache.shiro.web.servlet.AbstractShiroFilter#doFilterInternal
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
throws ServletException, IOException {
//....
//封装一下Request与Response
final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
//生成Subject,生成时会获取SessionID,如果存在,根据Session生成Subject
final Subject subject = createSubject(request, response);
//用Subject提供的接口来绑定Subject到线程,并执行后续操作
subject.execute(new Callable() {
public Object call() throws Exception {
updateSessionLastAccessTime(request, response);
executeChain(request, response, chain);
return null;
}
});
//....
}
//其中,使用subject.execute来执行代码,保证线程与数据的绑定与解绑
//org.apache.shiro.subject.support.SubjectCallable#call
public V call() throws Exception {
try {
threadState.bind();
return doCall(this.callable);
} finally {
threadState.restore();
}
}
//获取SessionID
/**
getSessionId:273, DefaultWebSessionManager (org.apache.shiro.web.session.mgt)
...
resolveSession:446, DefaultSecurityManager (org.apache.shiro.mgt)
...
buildSubject:845, Subject$Builder (org.apache.shiro.subject)
*/
//org.apache.shiro.web.session.mgt.DefaultWebSessionManager#getSessionId(org.apache.shiro.session.mgt.SessionKey)
@Override
public Serializable getSessionId(SessionKey key) {
Serializable id = super.getSessionId(key);
if (id == null && WebUtils.isWeb(key)) {
ServletRequest request = WebUtils.getRequest(key);
ServletResponse response = WebUtils.getResponse(key);
id = getSessionId(request, response);
}
return id;
}
题外话:Shiro-web中的Session是直接利用了HttpSession,对其做了一层封装。Shiro默认也提供了一种Session——SimpleSession。
二、手写实现简单验证
先约定一下接口:
- 接口统一使用"/api",POST方式传入JSON数据,使用cmd来区分实际接口,如:
{"cmd":"logout"}
- SessionID前端使用X-TOKEN头传入
通用配置就不列出来了,Shiro配置相当简化,只要提供一个Realm的配置即可:
1. Shiro配置
@Configuration
@Import({ShiroBeanConfiguration.class,
ShiroConfiguration.class})
public class ShiroConfig {
/**
* 自定义安全域,用户验证、权限等数据在此提供
* @return
*/
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm();
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myShiroRealm;
}
/**
* 凭证匹配器,这里的hash算法用来将前台传入的数据做处理
*
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashIterations(1);//散列的次数,比如散列两次,相当于 md5(md5(""));
return hashedCredentialsMatcher;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
*
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
//绑定SecurityManager
@Resource
private SecurityManager securityManager;
@PostConstruct
private void initStaticSecurityManager() {
SecurityUtils.setSecurityManager(securityManager);
}
2.使用
用的时候也很简单
- /api接口中,绑定Subject到进程,再调用后续服务
//首先解绑验证数据,保证Request开始的纯洁性
ThreadContext.unbindSubject();
if(StringUtils.isNotEmpty(request.getHeader("X-TOKEN"))){
//指定SessionID创建Subject
Subject subject = (new Subject.Builder()).sessionId(request.getHeader("X-TOKEN")).buildSubject();
if(subject != null){
//验证数据绑定线程数据
ThreadContext.bind(subject);
}
}
- 用户登录
Subject subject = SecurityUtils.getSubject();
subject.login(new UsernamePasswordToken(username,password));
- 权限控制,直接对相关函数做注解
//需要登录
@RequiresAuthentication
//需要权限
@RequiresPermissions("user:add")
//需要角色
@RequiresRoles("admin")
三、再总结
这当然只是自己的瞎折腾,web项目还是建议直接使用Shiro-web来实现,相当简洁优雅。不过生命在于折腾,对于Shiro的设计模式、web验证等又多了许多新的认识。
更多推荐
所有评论(0)