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 | 更新時間 |