[英]What is the proper way to specify a xsd within a spring bean.xml
[英]When using Spring Security, what is the proper way to obtain current username (i.e. SecurityContext) information in a bean?
我有一個使用 Spring Security 的 Spring MVC Web 應用程序。 我想知道當前登錄用戶的用戶名。 我正在使用下面給出的代碼片段。 這是公認的方式嗎?
我不喜歡在這個控制器中調用靜態方法 - 這違背了 Spring 的全部目的,恕我直言。 有沒有辦法將應用程序配置為注入當前的 SecurityContext 或當前的身份驗證?
@RequestMapping(method = RequestMethod.GET)
public ModelAndView showResults(final HttpServletRequest request...) {
final String currentUser = SecurityContextHolder.getContext().getAuthentication().getName();
...
}
如果您使用的是Spring 3 ,最簡單的方法是:
@RequestMapping(method = RequestMethod.GET)
public ModelAndView showResults(final HttpServletRequest request, Principal principal) {
final String currentUser = principal.getName();
}
自從回答這個問題以來,Spring 世界發生了很多變化。 Spring 簡化了在控制器中獲取當前用戶的過程。 對於其他bean,Spring采納了作者的建議,簡化了'SecurityContextHolder'的注入。 更多細節在評論中。
這是我最終采用的解決方案。 我不想在我的控制器中使用SecurityContextHolder
,而是想注入一些在底層使用SecurityContextHolder
東西,但從我的代碼中抽象出類似單例的類。 除了滾動我自己的界面之外,我發現沒有辦法做到這一點,如下所示:
public interface SecurityContextFacade {
SecurityContext getContext();
void setContext(SecurityContext securityContext);
}
現在,我的控制器(或任何 POJO)看起來像這樣:
public class FooController {
private final SecurityContextFacade securityContextFacade;
public FooController(SecurityContextFacade securityContextFacade) {
this.securityContextFacade = securityContextFacade;
}
public void doSomething(){
SecurityContext context = securityContextFacade.getContext();
// do something w/ context
}
}
而且,由於接口是一個解耦點,單元測試很簡單。 在這個例子中,我使用 Mockito:
public class FooControllerTest {
private FooController controller;
private SecurityContextFacade mockSecurityContextFacade;
private SecurityContext mockSecurityContext;
@Before
public void setUp() throws Exception {
mockSecurityContextFacade = mock(SecurityContextFacade.class);
mockSecurityContext = mock(SecurityContext.class);
stub(mockSecurityContextFacade.getContext()).toReturn(mockSecurityContext);
controller = new FooController(mockSecurityContextFacade);
}
@Test
public void testDoSomething() {
controller.doSomething();
verify(mockSecurityContextFacade).getContext();
}
}
接口的默認實現如下所示:
public class SecurityContextHolderFacade implements SecurityContextFacade {
public SecurityContext getContext() {
return SecurityContextHolder.getContext();
}
public void setContext(SecurityContext securityContext) {
SecurityContextHolder.setContext(securityContext);
}
}
最后,生產 Spring 配置如下所示:
<bean id="myController" class="com.foo.FooController">
...
<constructor-arg index="1">
<bean class="com.foo.SecurityContextHolderFacade">
</constructor-arg>
</bean>
Spring,一個所有事物的依賴注入容器,沒有提供一種注入類似東西的方法,這似乎有點愚蠢。 我知道SecurityContextHolder
是從 acegi 繼承的,但仍然如此。 問題是,它們非常接近 - 如果只有SecurityContextHolder
有一個 getter 來獲取底層SecurityContextHolderStrategy
實例(這是一個接口),您可以注入它。 事實上,我什至為此打開了一個 Jira 問題。
最后一件事 - 我剛剛大大改變了我之前在這里的答案。 如果您好奇,請查看歷史記錄,但正如一位同事向我指出的那樣,我之前的答案在多線程環境中不起作用。 底層SecurityContextHolderStrategy
使用SecurityContextHolder
,默認情況下,的一個實例ThreadLocalSecurityContextHolderStrategy
,其存儲SecurityContext
S IN一個ThreadLocal
。 因此,在初始化時將SecurityContext
直接注入 bean 不一定是一個好主意 - 在多線程環境中,每次可能需要從ThreadLocal
檢索它,因此檢索到正確的。
我同意必須查詢當前用戶的 SecurityContext 很糟糕,這似乎是處理這個問題的一種非常非 Spring 的方式。
我寫了一個靜態的“helper”類來處理這個問題; 它很臟,因為它是一種全局和靜態方法,但我認為如果我們更改與安全相關的任何內容,至少我只需要在一個地方更改細節:
/**
* Returns the domain User object for the currently logged in user, or null
* if no User is logged in.
*
* @return User object for the currently logged in user, or null if no User
* is logged in.
*/
public static User getCurrentUser() {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal()
if (principal instanceof MyUserDetails) return ((MyUserDetails) principal).getUser();
// principal object is either null or represents anonymous user -
// neither of which our domain User object can represent - so return null
return null;
}
/**
* Utility method to determine if the current user is logged in /
* authenticated.
* <p>
* Equivalent of calling:
* <p>
* <code>getCurrentUser() != null</code>
*
* @return if user is logged in
*/
public static boolean isLoggedIn() {
return getCurrentUser() != null;
}
為了讓它只顯示在你的 JSP 頁面中,你可以使用 Spring Security Tag Lib:
http://static.springsource.org/spring-security/site/docs/3.0.x/reference/taglibs.html
要使用任何標簽,您必須在 JSP 中聲明安全標簽庫:
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>
然后在一個jsp頁面中做這樣的事情:
<security:authorize access="isAuthenticated()">
logged in as <security:authentication property="principal.username" />
</security:authorize>
<security:authorize access="! isAuthenticated()">
not logged in
</security:authorize>
注意:正如@SBerg413 在評論中提到的,您需要添加
使用表達式=“真”
到 security.xml 配置中的“http”標記以使其工作。
如果您使用的是 Spring Security ver >= 3.2,則可以使用@AuthenticationPrincipal
注解:
@RequestMapping(method = RequestMethod.GET)
public ModelAndView showResults(@AuthenticationPrincipal CustomUser currentUser, HttpServletRequest request) {
String currentUsername = currentUser.getUsername();
// ...
}
在這里, CustomUser
是一個自定義對象實現UserDetails
由自定義返回UserDetailsService
。
更多信息可以在 Spring Security 參考文檔的@AuthenticationPrincipal章節中找到。
我通過 HttpServletRequest.getUserPrincipal(); 獲得經過身份驗證的用戶;
例子:
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.support.RequestContext;
import foo.Form;
@Controller
@RequestMapping(value="/welcome")
public class IndexController {
@RequestMapping(method=RequestMethod.GET)
public String getCreateForm(Model model, HttpServletRequest request) {
if(request.getUserPrincipal() != null) {
String loginName = request.getUserPrincipal().getName();
System.out.println("loginName : " + loginName );
}
model.addAttribute("form", new Form());
return "welcome";
}
}
在 Spring 3+ 中,您有以下選項。
選項1 :
@RequestMapping(method = RequestMethod.GET)
public String currentUserNameByPrincipal(Principal principal) {
return principal.getName();
}
選項2:
@RequestMapping(method = RequestMethod.GET)
public String currentUserNameByAuthentication(Authentication authentication) {
return authentication.getName();
}
選項 3:
@RequestMapping(method = RequestMethod.GET)
public String currentUserByHTTPRequest(HttpServletRequest request) {
return request.getUserPrincipal().getName();
}
選項 4:花式一:查看此了解更多詳細信息
public ModelAndView someRequestHandler(@ActiveUser User activeUser) {
...
}
我只會這樣做:
request.getRemoteUser();
是的,靜態通常是不好的 - 通常,但在這種情況下,靜態是您可以編寫的最安全的代碼。 由於安全上下文將 Principal 與當前運行的線程相關聯,因此最安全的代碼將盡可能直接地從線程訪問靜態。 將訪問隱藏在注入的包裝類后面,為攻擊者提供了更多攻擊點。 他們不需要訪問代碼(如果 jar 被簽名,他們將很難更改代碼),他們只需要一種覆蓋配置的方法,這可以在運行時完成或將一些 XML 滑到類路徑上。 即使在簽名代碼中使用注解注入也可以被外部 XML 覆蓋。 這樣的 XML 可能會向正在運行的系統注入一個流氓主體。 這可能就是為什么在這種情況下 Spring 會做一些與 Spring 不同的事情。
對於我寫的最后一個 Spring MVC 應用程序,我沒有注入 SecurityContext 持有者,但我確實有一個基本控制器,我有兩個與此相關的實用程序方法...... isAuthenticated() & getUsername()。 在內部,他們執行您描述的靜態方法調用。
至少,如果您以后需要重構,它只會出現一次。
您可以使用 Spring AOP 方法。 例如,如果您有一些服務,則需要知道當前的委托人。 您可以引入自定義注釋,即 @Principal ,這表明該服務應該是主體相關的。
public class SomeService {
private String principal;
@Principal
public setPrincipal(String principal){
this.principal=principal;
}
}
然后在我認為需要擴展 MethodBeforeAdvice 的建議中,檢查特定服務是否具有 @Principal 注釋並注入主體名稱,或者將其設置為“ANONYMOUS”。
唯一的問題是,即使在使用 Spring Security 進行身份驗證后,容器中也不存在用戶/主體 bean,因此依賴注入它會很困難。 在我們使用 Spring Security 之前,我們將創建一個具有當前 Principal 的 session-scoped bean,將它注入到“AuthService”中,然后將該 Service 注入到應用程序中的大多數其他服務中。 因此,這些服務將簡單地調用 authService.getCurrentUser() 來獲取對象。 如果您的代碼中有一個地方可以在會話中獲得對同一 Principal 的引用,則只需將其設置為會話范圍 bean 上的屬性即可。
如果您使用的是 Spring 3 並且需要在控制器中使用經過身份驗證的主體,那么最好的解決方案是執行以下操作:
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
@Controller
public class KnoteController {
@RequestMapping(method = RequestMethod.GET)
public java.lang.String list(Model uiModel, UsernamePasswordAuthenticationToken authToken) {
if (authToken instanceof UsernamePasswordAuthenticationToken) {
user = (User) authToken.getPrincipal();
}
...
}
我現在用的是@AuthenticationPrincipal
注釋中@Controller
類以及在@ControllerAdvicer
注釋的。 前任。:
@ControllerAdvice
public class ControllerAdvicer
{
private static final Logger LOGGER = LoggerFactory.getLogger(ControllerAdvicer.class);
@ModelAttribute("userActive")
public UserActive currentUser(@AuthenticationPrincipal UserActive currentUser)
{
return currentUser;
}
}
其中UserActive
是我用於登錄用戶服務的類,從org.springframework.security.core.userdetails.User
擴展而來。 就像是:
public class UserActive extends org.springframework.security.core.userdetails.User
{
private final User user;
public UserActive(User user)
{
super(user.getUsername(), user.getPasswordHash(), user.getGrantedAuthorities());
this.user = user;
}
//More functions
}
真的很容易。
將Principal
定義為控制器方法中的依賴項,spring 將在調用時將當前經過身份驗證的用戶注入到您的方法中。
嘗試這個
認證身份驗證 = SecurityContextHolder.getContext().getAuthentication();
String userName = authentication.getName();
我喜歡在 freemarker 頁面上分享我支持用戶詳細信息的方式。 一切都非常簡單,工作完美!
您只需要在default-target-url
(表單登錄后的頁面)上放置身份驗證重新請求 這是我對該頁面的 Controler 方法:
@RequestMapping(value = "/monitoring", method = RequestMethod.GET)
public ModelAndView getMonitoringPage(Model model, final HttpServletRequest request) {
showRequestLog("monitoring");
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String userName = authentication.getName();
//create a new session
HttpSession session = request.getSession(true);
session.setAttribute("username", userName);
return new ModelAndView(catalogPath + "monitoring");
}
這是我的 ftl 代碼:
<@security.authorize ifAnyGranted="ROLE_ADMIN, ROLE_USER">
<p style="padding-right: 20px;">Logged in as ${username!"Anonymous" }</p>
</@security.authorize>
就是這樣,用戶名將在授權后出現在每個頁面上。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.