[java] Spring Security를 사용할 때 Bean에서 현재 사용자 이름 (예 : SecurityContext) 정보를 얻는 올바른 방법은 무엇입니까?
Spring Security를 사용하는 Spring MVC 웹 앱이 있습니다. 현재 로그인 한 사용자의 사용자 이름을 알고 싶습니다. 아래 주어진 코드 스 니펫을 사용하고 있습니다. 이것이 허용되는 방법입니까?
나는이 컨트롤러 내에서 정적 메소드를 호출하는 것을 좋아하지 않는다. 대신 현재 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) {
...
}