При использовании Spring Security, каков правильный способ получить текущее имя пользователя (т. е. SecurityContext) информацию в бобе?
У меня есть веб-приложение Spring MVC, которое использует Spring Security. Я хочу знать имя пользователя, который в данный момент вошел в систему. Я использую фрагмент кода, приведенный ниже . Это общепринятый способ?
мне не нравится иметь вызов статического метода внутри этого контроллера - это побеждает всю цель Spring, IMHO. Есть ли способ настроить приложение, чтобы вместо него был введен текущий SecurityContext или текущая аутентификация?
@RequestMapping(method = RequestMethod.GET)
public ModelAndView showResults(final HttpServletRequest request...) {
final String currentUser = SecurityContextHolder.getContext().getAuthentication().getName();
...
}
17 ответов:
Если вы используете Весна 3, самый простой способ сделать это:
@RequestMapping(method = RequestMethod.GET) public ModelAndView showResults(final HttpServletRequest request, Principal principal) { final String currentUser = principal.getName(); }
многое изменилось в весеннем мире с тех пор, как был дан ответ на этот вопрос. Весна упростила получение текущего пользователя в контроллере. Для других бобов Весна приняла предложения автора и упростила инъекцию "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); } }и, наконец, конфигурация рабочей пружины выглядит так:
<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непосредственно в Боб во время инициализации-это возможно, потребуется извлечь из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 Lib:
http://static.springsource.org/spring-security/site/docs/3.0.x/reference/taglibs.html
чтобы использовать любой из тегов, вы должны иметь безопасность taglib объявлен в вашем 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, вам нужно будет добавить
use-expressions= "true"
к тегу "http" в безопасности.в xml файле для этой работы.
Я получаю аутентифицированного пользователя 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 Security ver >= 3.2, вы можете использовать
@AuthenticationPrincipalаннотация:@RequestMapping(method = RequestMethod.GET) public ModelAndView showResults(@AuthenticationPrincipal CustomUser currentUser, HttpServletRequest request) { String currentUsername = currentUser.getUsername(); // ... }здесь
CustomUserпользовательский объект, который реализуетUserDetailsэто возвращается пользовательскимUserDetailsService.дополнительную информацию можно найти в @AuthenticationPrincipal глава справочных документов Spring Security.
весной 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: Fancy one:проверьте это для более подробной информации
public ModelAndView someRequestHandler(@ActiveUser User activeUser) { ... }
да, статика вообще плохая-вообще, но в этом случае статика-это самый безопасный код, который вы можете написать. Поскольку контекст безопасности связывает участника с текущим потоком, наиболее безопасный код будет обращаться к статическому из потока как можно напрямую. Скрытие доступа за введенным классом-оболочкой предоставляет злоумышленнику больше очков для атаки. Им не понадобится доступ к коду (который им будет трудно изменить, если jar был подписан), им просто нужен способ переопределить конфигурацию, что можно сделать во время выполнения или проскальзывания некоторого XML на путь к классам. Даже использование инъекции аннотаций в подписанном коде будет переопределено с помощью внешнего XML. Такой XML может внедрить в работающую систему неуправляемый Принципал. Вероятно, именно поэтому весна делает что-то настолько не похожее на весну в этом случае.
для последнего весеннего приложения MVC, которое я написал, Я не вводил держатель SecurityContext, но у меня был базовый контроллер, с которым у меня было два служебных метода, связанных с этим ... isAuthenticated () & getUsername (). Внутренне они выполняют описанный вами вызов статического метода.
по крайней мере, тогда это только в одном месте, если возникнет такая необходимость рефакторинга.
вы можете использовать Spring AOP aproach. Например, если у вас есть какая-то служба, которая должна знать текущего участника. Вы можете ввести пользовательскую аннотацию, т. е. @Principal, которая указывает, что эта служба должна быть основной зависимой.
public class SomeService { private String principal; @Principal public setPrincipal(String principal){ this.principal=principal; } }затем в вашем совете, который, я думаю, должен расширить MethodBeforeAdvice, проверьте, что конкретная служба имеет аннотацию @Principal и вводит имя участника или вместо этого устанавливает его в "анонимный".
единственная проблема заключается в том, что даже после аутентификации с помощью Spring Security пользовательский/основной компонент не существует в контейнере, поэтому его внедрение в зависимости будет затруднено. Прежде чем мы использовали Spring Security, мы бы создали компонент с областью сеанса, который имел текущего участника, внедрить его в "AuthService", а затем внедрить эту службу в большинство других служб в приложении. Поэтому эти службы просто вызовут authService.getCurrentUser () для получения объекта. Если вы есть место в коде, где вы получаете ссылку на тот же Принципал в сеансе, вы можете просто установить его в качестве свойства на вашем сеансовом компоненте.
лучшее решение, если вы используете 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 }очень легко.
попробуй такое
проверка подлинности проверка подлинности = SecurityContextHolder.getContext ().getAuthentication ();
Строка userName = аутентификация.getName ();
определение
Principalкак зависимость в вашем методе контроллера и spring будет вводить текущего аутентифицированного пользователя в ваш метод при вызове.
Я хотел бы поделиться своим способом поддержки сведений о пользователе на странице freemarker. Все очень просто и работает отлично!
вы просто должны разместить rerequest проверки подлинности на
default-target-url(страница после входа) Это мой метод контроллера для этой страницы:@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"); }и это мой сверхсветовой код:
<@security.authorize ifAnyGranted="ROLE_ADMIN, ROLE_USER"> <p style="padding-right: 20px;">Logged in as ${username!"Anonymous" }</p> </@security.authorize>и это все, имя пользователя появится на каждой странице после авторизации.
Comments