Spring Security를 사용할 때 Bean에서 현재 사용자 이름 (예 : SecurityContext) 정보를 얻는 올바른 방법은 무엇입니까?
Spring Security를 사용하는 Spring MVC 웹 앱이 있습니다. 현재 로그인 한 사용자의 사용자 이름을 알고 싶습니다. 아래 주어진 코드 스 니펫을 사용하고 있습니다. 이것이 허용되는 방법입니까?
나는이 컨트롤러 내에서 정적 메소드를 호출하는 것을 좋아하지 않는다. 이것은 Spring의 전체 목적을 무효화한다 .IMHO. 대신 현재 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은 저자의 제안을 채택하고 '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
기본 SecurityContextHolderStrategy
인스턴스 (인터페이스) 를 얻는 게터 만 있다면 그것을 주입 할 수 있습니다. 사실, 나는 심지어 Jira 문제 를 그 효과로 열었습니다 .
마지막 한 가지-나는 이전에 내가 가진 대답을 크게 바꿨습니다. 궁금한 점이 있다면 역사를 확인하십시오.하지만 동료가 지적했듯이 이전 답변은 멀티 스레드 환경에서 작동하지 않습니다. 기본적으로 SecurityContextHolderStrategy
사용되는 SecurityContextHolder
기본은의 인스턴스이며 ThreadLocalSecurityContextHolderStrategy
를에 저장 SecurityContext
합니다 ThreadLocal
. 따라서 SecurityContext
초기화 시간에 Bean에 직접 직접 삽입하는 것이 좋은 생각은 아닙니다 ThreadLocal
. 멀티 스레드 환경에서 매번 검색해야 할 수 있으므로 올바른 것을 검색해야합니다.
현재 사용자의 악취에 대해 SecurityContext를 쿼리해야한다는 점에 동의합니다.
이 문제를 해결하기 위해 정적 "헬퍼"클래스를 작성했습니다. 전역적이고 정적 인 방법이라는 것은 더럽지 만 보안과 관련된 것을 변경하면 적어도 한 곳에서만 세부 정보를 변경해야한다고 생각합니다.
/**
* 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가 선언되어 있어야합니다.
<%@ 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의 의견에서 언급했듯이 추가해야합니다.
use-expressions = "true"
이것이 작동하려면 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
custom에 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) {
...
}
예, 정적은 일반적으로 좋지 않습니다. 일반적으로이 경우 정적은 작성할 수있는 가장 안전한 코드입니다. 보안 컨텍스트는 Principal을 현재 실행중인 스레드와 연결하므로 가장 안전한 코드는 가능한 한 스레드에서 직접 정적에 액세스합니다. 주입 된 랩퍼 클래스 뒤에 액세스를 숨기면 공격자에게 더 많은 공격 포인트가 제공됩니다. 코드에 액세스 할 필요가 없으며 (jar이 서명 된 경우 변경하기가 어려울 수 있음) 런타임에 수행하거나 일부 XML을 클래스 경로에 넣을 수있는 구성을 재정의하는 방법이 필요합니다. 서명 된 코드에서 주석 삽입을 사용하더라도 외부 XML로 재정의 할 수 있습니다. 이러한 XML은 실행중인 시스템에 불량 사용자를 주입 할 수 있습니다. 이것은 아마도 Spring이이 경우 봄과 같은 것을하지 않는 이유 일 것입니다.
나는 이것을 할 것입니다 :
request.getRemoteUser();
내가 쓴 마지막 Spring MVC 앱의 경우 SecurityContext 홀더를 주입하지 않았지만 이것과 관련된 두 가지 유틸리티 메소드가있는 기본 컨트롤러가 있습니다 ... isAuthenticated () & getUsername (). 내부적으로 그들은 당신이 설명한 정적 메소드 호출을 수행합니다.
나중에 리팩토링해야 할 경우 적어도 한 번에 있습니다.
Spring AOP aproach를 사용할 수 있습니다. 예를 들어 서비스가있는 경우 현재 주체를 알아야합니다. @Principal과 같은 사용자 정의 주석을 도입 할 수 있습니다.이 서비스는이 서비스가 주체에 의존해야 함을 나타냅니다.
public class SomeService {
private String principal;
@Principal
public setPrincipal(String principal){
this.principal=principal;
}
}
그런 다음 MethodBeforeAdvice를 확장해야한다고 생각하는 특정 서비스에 @Principal 주석이 있는지 확인하고 Principal 이름을 삽입하거나 대신 'ANONYMOUS'로 설정하십시오.
유일한 문제는 Spring Security로 인증 한 후에도 사용자 / 주체 Bean이 컨테이너에 없기 때문에 의존성 주입이 어렵다는 것입니다. Spring Security를 사용하기 전에 현재 프린시 펄이있는 세션 범위 Bean을 작성하여 "AuthService"에 삽입 한 다음 해당 서비스를 애플리케이션의 다른 서비스 대부분에 주입합니다. 따라서 해당 서비스는 단순히 authService.getCurrentUser ()를 호출하여 객체를 가져옵니다. 코드에서 세션의 동일한 프린시 펄에 대한 참조를 얻을 수있는 위치가 있으면 세션 범위 Bean의 특성으로 간단히 설정할 수 있습니다.
이 시도
인증 인증 = SecurityContextHolder.getContext (). getAuthentication ();
문자열 userName = authentication.getName ();
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은 호출시 메소드에 현재 인증 된 사용자를 주입합니다.
프리 마커 페이지에서 사용자 세부 정보를 지원하는 방법을 공유하고 싶습니다. 모든 것이 매우 간단하고 완벽하게 작동합니다!
인증 요청을 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>
그게 다야, 인증 후 사용자 이름이 모든 페이지에 나타납니다.
'IT' 카테고리의 다른 글
MySQL VARCHAR 최대 크기는 얼마입니까? (0) | 2020.03.24 |
---|---|
미디어 쿼리는 화면 대신 div 요소를 기반으로 크기를 조정할 수 있습니까? (0) | 2020.03.24 |
NuGet 패키지 위치를 변경할 수 있습니까? (0) | 2020.03.24 |
"is None"과 "== None"의 차이점은 무엇입니까 (0) | 2020.03.24 |
Java에서 String을 InputStreamReader로 바꾸는 방법은 무엇입니까? (0) | 2020.03.24 |