shiro token 分析
1.ShiroConfig.java 定义匿名用户可以访问的资源
filterMap.put("/webjars/**", "anon");
filterMap.put("/druid/**", "anon");
filterMap.put("/api/**", "anon");
filterMap.put("/sys/login", "anon");
filterMap.put("/**/*.css", "anon");
filterMap.put("/**/*.js", "anon");
filterMap.put("/**/*.html", "anon");
filterMap.put("/fonts/**", "anon");
filterMap.put("/plugins/**", "anon");
filterMap.put("/swagger/**", "anon");
filterMap.put("/favicon.ico", "anon");
filterMap.put("/", "anon");
filterMap.put("/**", "oauth2"); --除了anon,拦截其他所有请求
2.OAuth2Filter.java 基于shiro的全局过滤器
继承AuthenticatingFilter 实现createToken、isAccessAllowed、onAccessDenied、onLoginFailure等抽象方法
import io.renren.common.utils.R;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpStatus;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class OAuth2Filter extends AuthenticatingFilter {
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
//获取请求token
String token = getRequestToken((HttpServletRequest) request);
if(StringUtils.isBlank(token)){
return null;
}
return new OAuth2Token(token);
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
return false;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
//获取请求token,如果token不存在,直接返回401
String token = getRequestToken((HttpServletRequest) request);
if(StringUtils.isBlank(token)){
HttpServletResponse httpResponse = (HttpServletResponse) response;
String json = new Gson().toJson(R.error(HttpStatus.SC_UNAUTHORIZED, "invalid token"));
httpResponse.getWriter().print(json);
return false;
}
System.out.println("onAccessDenied-----------------------onAccessDenied");
return executeLogin(request, response);
}
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setContentType("application/json;charset=utf-8");
try {
//处理登录失败的异常
Throwable throwable = e.getCause() == null ? e : e.getCause();
R r = R.error(HttpStatus.SC_UNAUTHORIZED, throwable.getMessage());
String json = new Gson().toJson(r);
httpResponse.getWriter().print(json);
} catch (IOException e1) {
}
return false;
}
/**
* 获取请求的token
*/
private String getRequestToken(HttpServletRequest httpRequest){
//从header中获取token
String token = httpRequest.getHeader("token");
//如果header中不存在token,则从参数中获取token
if(StringUtils.isBlank(token)){
token = httpRequest.getParameter("token");
}
return token;
}
}
如果成功获得token 则继续调用父类中executeLogin方法,此方法实现如下
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception { AuthenticationToken token = createToken(request, response); if (token == null) { String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " + "must be created in order to execute a login attempt."; throw new IllegalStateException(msg); } try {
// 创建主题,然后继续调用Realm中的登入认证方法doGetAuthenticationInfo Subject subject = getSubject(request, response); subject.login(token); return onLoginSuccess(token, subject, request, response); } catch (AuthenticationException e) { return onLoginFailure(token, e, request, response); } }
调用子类中的createToken方法获得token对象,将token对象赋值给shiro subject 对象,从而在后面的认证方法中获得token
3.将OAuth2Realm 注册到Shiro Seurity中,ShiroConfig.securityManager

1 package io.renren.config; 2 3 import io.renren.modules.sys.oauth2.OAuth2Filter; 4 import io.renren.modules.sys.oauth2.OAuth2Realm; 5 import org.apache.shiro.mgt.SecurityManager; 6 import org.apache.shiro.session.mgt.SessionManager; 7 import org.apache.shiro.spring.LifecycleBeanPostProcessor; 8 import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; 9 import org.apache.shiro.spring.web.ShiroFilterFactoryBean; 10 import org.apache.shiro.web.mgt.DefaultWebSecurityManager; 11 import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; 12 import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; 13 import org.springframework.context.annotation.Bean; 14 import org.springframework.context.annotation.Configuration; 15 16 import javax.servlet.Filter; 17 import java.util.HashMap; 18 import java.util.LinkedHashMap; 19 import java.util.Map; 20 21 22 @Configuration 23 public class ShiroConfig { 24 25 @Bean("sessionManager") 26 public SessionManager sessionManager(){ 27 DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); 28 sessionManager.setSessionValidationSchedulerEnabled(true); 29 sessionManager.setSessionIdCookieEnabled(false); 30 System.out.println("获得sessionManager:" + sessionManager); 31 return sessionManager; 32 } 33 34 @Bean("securityManager") 35 public SecurityManager securityManager(OAuth2Realm oAuth2Realm, SessionManager sessionManager) { 36 DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); 37 securityManager.setRealm(oAuth2Realm); 38 securityManager.setSessionManager(sessionManager); 39 System.out.println("获得SecurityManager:" + securityManager); 40 return securityManager; 41 } 42 43 @Bean("shiroFilter") 44 public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { 45 ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean(); 46 shiroFilter.setSecurityManager(securityManager); 47 48 //oauth过滤 49 Map<String, Filter> filters = new HashMap<>(); 50 filters.put("oauth2", new OAuth2Filter()); 51 shiroFilter.setFilters(filters); 52 53 Map<String, String> filterMap = new LinkedHashMap<>(); 54 filterMap.put("/webjars/**", "anon"); 55 filterMap.put("/druid/**", "anon"); 56 filterMap.put("/api/**", "anon"); 57 filterMap.put("/sys/login", "anon"); 58 filterMap.put("/**/*.css", "anon"); 59 filterMap.put("/**/*.js", "anon"); 60 filterMap.put("/**/*.html", "anon"); 61 filterMap.put("/fonts/**", "anon"); 62 filterMap.put("/plugins/**", "anon"); 63 filterMap.put("/swagger/**", "anon"); 64 filterMap.put("/favicon.ico", "anon"); 65 filterMap.put("/", "anon"); 66 filterMap.put("/**", "oauth2"); 67 shiroFilter.setFilterChainDefinitionMap(filterMap); 68 System.out.println("获得shiroFilter:" + shiroFilter); 69 return shiroFilter; 70 } 71 72 @Bean("lifecycleBeanPostProcessor") 73 public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { 74 return new LifecycleBeanPostProcessor(); 75 } 76 77 @Bean 78 public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { 79 DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator(); 80 proxyCreator.setProxyTargetClass(true); 81 return proxyCreator; 82 } 83 84 @Bean 85 public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { 86 AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); 87 advisor.setSecurityManager(securityManager); 88 return advisor; 89 } 90 91 }
4.每次请求都会先调用OAuth2Realm中的doGetAuthenticationInfo方法验证token的合法性,然后再调用doGetAuthorizationInfo验证权限
5.通过common.js判断当前客户端是否缓存了token,如果没有则跳转至login.html
//登录token
var token = localStorage.getItem("token");
if(token == 'null'){
parent.location.href = baseURL + 'login.html';
}
6.登入页面输入用户名、密码之后 缓存token,并跳转至index.html

login: function () { var data = "username="+vm.username+"&password="+vm.password; $.ajax({ type: "POST", url: baseURL + "sys/login", data: data, dataType: "json", success: function(r){ if(r.code == 0){//登录成功 localStorage.setItem("token", r.token); parent.location.href ='index.html'; }else{ vm.error = true; vm.errorMsg = r.msg; } } }); }
7.LoginController.java
/** * 登录 */ @RequestMapping(value = "/sys/login", method = RequestMethod.POST) public Map<String, Object> login(String username, String password)throws IOException { //用户信息 SysUserEntity user = sysUserService.queryByUserName(username); //账号不存在、密码错误 if(user == null || !user.getPassword().equals(new Sha256Hash(password, user.getSalt()).toHex())) { return R.error("账号或密码不正确"); } //账号锁定 if(user.getStatus() == 0){ return R.error("账号已被锁定,请联系管理员"); } //生成token,并保存到数据库 R r = sysUserTokenService.createToken(user.getUserId()); return r; }
8. 数据库token表结构,该表结构改成redis即可实现sso单点登入功能
Field | Type | Comment | |
user_id | bigint(20) NOT NULL | ||
token | varchar(100) NOT NULL | token | |
expire_time | datetime NULL | 过期时间 | |
update_time | datetime NULL | 更新时间 |
posted on 2017-09-13 14:19 rigidwang 阅读(4653) 评论(0) 编辑 收藏 举报
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战