SecurityUtils.getSubject().getPrincipal()為null的問題
SecurityUtils.getSubject().getPrincipal()為null
我在項目中獲取getUserId(),和getUserName()獲取不到值。
他們都是通過SecurityUtils.getSubject().getPrincipal()去獲取的。
反復測試發(fā)現(xiàn)原因是 :在shiroConfig里面:
該方法,注意(是該接口名)被寫為anon,不通過驗證,即:
filterMap.put("/druid/**", “anon”);這種寫法是為了postman測試時不被攔截。
解決辦法
從頁面訪問后端不需要anon了。
anon是不做攔截,authc是走自定義攔截oauth2是自帶攔截
修改為:
filterMap.put("/druid/**", “authc”);shiro SecurityUtils.getSubject()深度分析
1.總的來說,SecurityUtils.getSubject()是每個請求創(chuàng)建一個Subject, 并保存到ThreadContext的resources(ThreadLocal<Map<Object, Object>>)變量中,也就是一個http請求一個subject,并綁定到當前過程。
問題來了:.subject.login()登陸認證成功后,下一次請求如何知道是那個用戶的請求呢?
友情提示:本文唯一可以讀一下的就是分析這個疑問,如果你已經(jīng)明白就不用往下看了。
首先給出內(nèi)部原理:1個請求1個Subject原理:由于ShiroFilterFactoryBean本質(zhì)是個AbstractShiroFilter過濾器,所以每次請求都會執(zhí)行doFilterInternal里面的createSubject方法。
猜想:因為subject是綁定到當前線程,這肯定需要一個中介存儲狀態(tài)
public static Subject getSubject() {
Subject subject = ThreadContext.getSubject();
if (subject == null) {
subject = (new Builder()).buildSubject();
ThreadContext.bind(subject);
}
return subject;
} public abstract class ThreadContext {
private static final Logger log = LoggerFactory.getLogger(ThreadContext.class);
public static final String SECURITY_MANAGER_KEY = ThreadContext.class.getName() + "_SECURITY_MANAGER_KEY";
public static final String SUBJECT_KEY = ThreadContext.class.getName() + "_SUBJECT_KEY";
private static final ThreadLocal<Map<Object, Object>> resources = new ThreadContext.InheritableThreadLocalMap();
protected ThreadContext() {
}
public static Map<Object, Object> getResources() {
return (Map)(resources.get() == null ? Collections.emptyMap() : new HashMap((Map)resources.get()));
}
public static void setResources(Map<Object, Object> newResources) {
if (!CollectionUtils.isEmpty(newResources)) {
ensureResourcesInitialized();
((Map)resources.get()).clear();
((Map)resources.get()).putAll(newResources);
}
}
private static Object getValue(Object key) {
Map<Object, Object> perThreadResources = (Map)resources.get();
return perThreadResources != null ? perThreadResources.get(key) : null;
}
private static void ensureResourcesInitialized() {
if (resources.get() == null) {
resources.set(new HashMap());
}
}
public static Object get(Object key) {
if (log.isTraceEnabled()) {
String msg = "get() - in thread [" + Thread.currentThread().getName() + "]";
log.trace(msg);
}
Object value = getValue(key);
if (value != null && log.isTraceEnabled()) {
String msg = "Retrieved value of type [" + value.getClass().getName() + "] for key [" + key + "] bound to thread [" + Thread.currentThread().getName() + "]";
log.trace(msg);
}
return value;
}
public static void put(Object key, Object value) {
if (key == null) {
throw new IllegalArgumentException("key cannot be null");
} else if (value == null) {
remove(key);
} else {
ensureResourcesInitialized();
((Map)resources.get()).put(key, value);
if (log.isTraceEnabled()) {
String msg = "Bound value of type [" + value.getClass().getName() + "] for key [" + key + "] to thread [" + Thread.currentThread().getName() + "]";
log.trace(msg);
}
}
}
public static Object remove(Object key) {
Map<Object, Object> perThreadResources = (Map)resources.get();
Object value = perThreadResources != null ? perThreadResources.remove(key) : null;
if (value != null && log.isTraceEnabled()) {
String msg = "Removed value of type [" + value.getClass().getName() + "] for key [" + key + "]from thread [" + Thread.currentThread().getName() + "]";
log.trace(msg);
}
return value;
}
public static void remove() {
resources.remove();
}
public static SecurityManager getSecurityManager() {
return (SecurityManager)get(SECURITY_MANAGER_KEY);
}
public static void bind(SecurityManager securityManager) {
if (securityManager != null) {
put(SECURITY_MANAGER_KEY, securityManager);
}
}
public static SecurityManager unbindSecurityManager() {
return (SecurityManager)remove(SECURITY_MANAGER_KEY);
}
public static Subject getSubject() {
return (Subject)get(SUBJECT_KEY);
}
public static void bind(Subject subject) {
if (subject != null) {
put(SUBJECT_KEY, subject);
}
}
public static Subject unbindSubject() {
return (Subject)remove(SUBJECT_KEY);
}
private static final class InheritableThreadLocalMap<T extends Map<Object, Object>> extends InheritableThreadLocal<Map<Object, Object>> {
private InheritableThreadLocalMap() {
}
protected Map<Object, Object> childValue(Map<Object, Object> parentValue) {
return parentValue != null ? (Map)((HashMap)parentValue).clone() : null;
}
}
} subject登陸成功后,下一次請求如何知道是那個用戶的請求呢?
經(jīng)過源碼分析,核心實現(xiàn)如下DefaultSecurityManager類中:
public Subject createSubject(SubjectContext subjectContext) {
SubjectContext context = this.copy(subjectContext);
context = this.ensureSecurityManager(context);
context = this.resolveSession(context);
context = this.resolvePrincipals(context);
Subject subject = this.doCreateSubject(context);
this.save(subject);
return subject;
} 每次請求都會重新設(shè)置Session和Principals,看到這里大概就能猜到:如果是web工程,直接從web容器獲取httpSession,然后再從httpSession獲取Principals,本質(zhì)就是從cookie獲取用戶信息,然后每次都設(shè)置Principal,這樣就知道是哪個用戶的請求,并只得到這個用戶有沒有人認證成功,--本質(zhì):依賴于瀏覽器的cookie來維護session的
擴展,如果不是web容器的app,如何實現(xiàn)實現(xiàn)無狀態(tài)的會話
1.一般的作法會在header中帶有一個token,或者是在參數(shù)中,后臺根據(jù)這個token來進行校驗這個用戶的身份,但是這個時候,servlet中的session就無法保存,我們在這個時候,就要實現(xiàn)自己的會話創(chuàng)建,普通的作法就是重寫session與request的接口,然后在過濾器在把它替換成自己的request,所以得到的session也是自己的session,然后根據(jù)token來創(chuàng)建和維護會話
2.shiro實現(xiàn):
重寫shiro的sessionManage
import org.apache.shiro.session.mgt.SessionKey;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.Serializable;
import java.util.UUID;
/**
* @author zxj<br>
* 時間 2017/11/8 15:55
* 說明 ...
*/
public class StatelessSessionManager extends DefaultWebSessionManager {
/**
* 這個是服務(wù)端要返回給客戶端,
*/
public final static String TOKEN_NAME = "TOKEN";
/**
* 這個是客戶端請求給服務(wù)端帶的header
*/
public final static String HEADER_TOKEN_NAME = "token";
public final static Logger LOG = LoggerFactory.getLogger(StatelessSessionManager.class);
@Override
public Serializable getSessionId(SessionKey key) {
Serializable sessionId = key.getSessionId();
if(sessionId == null){
HttpServletRequest request = WebUtils.getHttpRequest(key);
HttpServletResponse response = WebUtils.getHttpResponse(key);
sessionId = this.getSessionId(request,response);
}
HttpServletRequest request = WebUtils.getHttpRequest(key);
request.setAttribute(TOKEN_NAME,sessionId.toString());
return sessionId;
}
@Override
protected Serializable getSessionId(ServletRequest servletRequest, ServletResponse servletResponse) {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String token = request.getHeader(HEADER_TOKEN_NAME);
if(token == null){
token = UUID.randomUUID().toString();
}
//這段代碼還沒有去查看其作用,但是這是其父類中所擁有的代碼,重寫完后我復制了過來...開始
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, token);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, isSessionIdUrlRewritingEnabled());
//這段代碼還沒有去查看其作用,但是這是其父類中所擁有的代碼,重寫完后我復制了過來...結(jié)束
return token;
}
}
@RequestMapping("/")
public void login(@RequestParam("code")String code, HttpServletRequest request){
Map<String,Object> data = new HashMap<>();
if(SecurityUtils.getSubject().isAuthenticated()){
//這里代碼著已經(jīng)登陸成功,所以自然不用再次認證,直接從rquest中取出就行了,
data.put(StatelessSessionManager.HEADER_TOKEN_NAME,getServerToken());
data.put(BIND,ShiroKit.getUser().getTel() != null);
response(data);
}
LOG.info("授權(quán)碼為:" + code);
AuthorizationService authorizationService = authorizationFactory.getAuthorizationService(Constant.clientType);
UserDetail authorization = authorizationService.authorization(code);
Oauth2UserDetail userDetail = (Oauth2UserDetail) authorization;
loginService.login(userDetail);
User user = userService.saveUser(userDetail,Constant.clientType.toString());
ShiroKit.getSession().setAttribute(ShiroKit.USER_DETAIL_KEY,userDetail);
ShiroKit.getSession().setAttribute(ShiroKit.USER_KEY,user);
data.put(BIND,user.getTel() != null);
//這里的代碼,必須放到login之執(zhí)行,因為login后,才會創(chuàng)建session,才會得到最新的token咯
data.put(StatelessSessionManager.HEADER_TOKEN_NAME,getServerToken());
response(data);
}
}import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author zxj<br>
* 時間 2017/11/8 15:40
* 說明 ...
*/
@Configuration
public class ShiroConfiguration {
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
return new LifecycleBeanPostProcessor();
}
/**
* 此處注入一個realm
* @param realm
* @return
*/
@Bean
public SecurityManager securityManager(Realm realm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setSessionManager(new StatelessSessionManager());
securityManager.setRealm(realm);
return securityManager;
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
Map<String,String> map = new LinkedHashMap<>();
map.put("/public/**","anon");
map.put("/login/**","anon");
map.put("/**","user");
bean.setFilterChainDefinitionMap(map);
return bean;
}
} 以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringMVC跨服務(wù)器上傳文件中出現(xiàn)405錯誤的解決
這篇文章主要介紹了SpringMVC跨服務(wù)器上傳文件中出現(xiàn)405錯誤的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09
springboot prototype設(shè)置多例不起作用的解決操作
這篇文章主要介紹了springboot prototype設(shè)置多例不起作用的解決操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09
java9的JShell小工具和編譯器兩種自動優(yōu)化方法
這篇文章主要介紹了java9的JShell小工具和編譯器兩種自動優(yōu)化方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-07-07

