Реализовать пользовательский AuthenticationProvider в Spring Security 2.06 - PullRequest
35 голосов
/ 28 декабря 2011

Я использую Spring Security для защиты веб-приложения Struts2.Из-за ограничений проекта я использую Spring Security 2.06.

Моя команда создала пользовательский API управления пользователями, который аутентифицирует пользователя после ввода параметров имени пользователя и пароля и возвращает пользовательский объект пользователя, содержащий список ролей.и другие атрибуты, такие как электронная почта, имя и т. д.

Насколько я понимаю, в типичном случае использования Spring Security для извлечения объекта UserDetails используется стандартный UserDetailsService;этот объект будет содержать (среди прочего) поле пароля, которое будет использоваться платформой для аутентификации пользователя.

В моем случае я хочу позволить нашему пользовательскому API выполнить аутентификацию, а затем вернуть пользовательский UserDetailsобъект, содержащий роли и другие атрибуты (электронная почта и т. д.).

После некоторых исследований я выяснил, что могу сделать это с помощью пользовательской реализации AuthenticationProvider.У меня также есть пользовательские реализации UserDetailsService и UserDetails.

Моя проблема в том, что я не совсем понимаю, что я должен возвращать в CustomAuthenticationProvider.Использую ли я здесь свой объект UserDetailsService?Это даже нужно?Извините, я действительно запутался.

CustomAuthenticationProvider:

public class CustomAuthenticationProvider implements AuthenticationProvider {

private Logger logger = Logger.getLogger(CustomAuthenticationProvider.class);

private UserDetailsService userDetailsService; //what am i supposed to do with this?

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    UsernamePasswordAuthenticationToken auth = (UsernamePasswordAuthenticationToken) authentication;
    String username = String.valueOf(auth.getPrincipal());
    String password = String.valueOf(auth.getCredentials());

    logger.info("username:" + username);
    logger.info("password:" + password);
    /* what should happen here? */

    return null;  //what do i return?
}

@Override
public boolean supports(Class aClass) {
    return true;  //To indicate that this authenticationprovider can handle the auth request. since there's currently only one way of logging in, always return true
}

public UserDetailsService getUserDetailsService() {
    return userDetailsService;
}

public void setUserDetailsService(UserDetailsService userDetailsService) {
    this.userDetailsService = userDetailsService;
}

}

applicationContext-security.xml:

<beans:bean id="customUserDetailsService" scope="prototype" class="com.test.testconsole.security.CustomUserDetailsService"/>

<beans:bean id="customAuthenticationProvider" class="com.test.testconsole.security.CustomAuthenticationProvider">
    <custom-authentication-provider />
    <beans:property name="userDetailsService" ref="customUserDetailsService" />
</beans:bean>

Подводя итог,вот что мне нужно:

  1. Пользователь входит в систему через веб-форму
  2. Аутентификация пользователя с использованием внутреннего API управления пользователями
  3. Для успешно аутентифицированных пользователей заполните GrantedAuthories.и т. д.
  4. Возврат пользовательской сущности, содержащей роли / полномочия и другие атрибуты, такие как электронная почта, имя и т. д. После этого я смогу получить доступ к этому объекту следующим образом ..

    //spring security get user name
    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    userName = auth.getName(); //get logged in username
    logger.info("username: " + userName);
    
    //spring security get user role
    GrantedAuthority[] authorities = auth.getAuthorities();
    userRole = authorities[0].getAuthority();
    logger.info("user role: " + userRole);
    

Надеюсь, это имеет смысл.Любая помощь или указатели будут оценены!

Спасибо!

Обновление:

Я добился определенного прогресса, я думаю.

У меня есть пользовательскийОбъект аутентификации, реализующий интерфейс аутентификации:

public class CustomAuthentication implements Authentication {

    String name;
    GrantedAuthority[] authorities;
    Object credentials;
    Object details;
    Object principal;
    boolean authenticated;

    public CustomAuthentication(String name, GrantedAuthority[] authorities, Object credentials, Object details, Object principal, boolean
                                authenticated){
        this.name=name;
        this.authorities=authorities;
        this.details=details;
        this.principal=principal;
        this.authenticated=authenticated;

    }
    @Override
    public GrantedAuthority[] getAuthorities() {
        return new GrantedAuthority[0];  //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public Object getCredentials() {
        return null;  //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public Object getDetails() {
        return null;  //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public Object getPrincipal() {
        return null;  //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public boolean isAuthenticated() {
        return false;  //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
        //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public String getName() {
        return null;  
    }
}

и обновивший мой класс CustomerAuthenticationProvider:

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        UsernamePasswordAuthenticationToken auth = (UsernamePasswordAuthenticationToken) authentication;
        String username = String.valueOf(auth.getPrincipal());
        String password = String.valueOf(auth.getCredentials());

        logger.info("username:" + username);
        logger.info("password:" + password);

        //no actual validation done at this time

        GrantedAuthority[] authorities = new GrantedAuthorityImpl[1];
        authorities[0] = new GrantedAuthorityImpl("ROLE_USER");

        CustomAuthentication customAuthentication = new CustomAuthentication("TestMerchant",authorities,"details",username,password,true);

    return customAuthentication;

    //return new UsernamePasswordAuthenticationToken(username,password,authorities); 
}

Работает, если я возвращаю объект UsernamePasswordAuthenticationToken, но если я пытаюсь вернуть CustomAuthentication, я получаюследующая ошибка:

java.lang.ClassCastException: com.test.testconsole.security.CustomAuthentication cannot be cast to org.springframework.security.providers.UsernamePasswordAuthenticationToken
    at com.test.testconsole.security.CustomAuthenticationProvider.authenticate(CustomAuthenticationProvider.java:27)
    at org.springframework.security.providers.ProviderManager.doAuthentication(ProviderManager.java:188)
    at org.springframework.security.AbstractAuthenticationManager.authenticate(AbstractAuthenticationManager.java:46)
    at org.springframework.security.intercept.AbstractSecurityInterceptor.authenticateIfRequired(AbstractSecurityInterceptor.java:319)
    at org.springframework.security.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:258)
    at org.springframework.security.intercept.web.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:106)
    at org.springframework.security.intercept.web.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:83)
    at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390)
    at org.springframework.security.ui.SessionFixationProtectionFilter.doFilterHttp(SessionFixationProtectionFilter.java:67)
    at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53)
    at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390)
    at org.springframework.security.ui.ExceptionTranslationFilter.doFilterHttp(ExceptionTranslationFilter.java:101)
    at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53)
    at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390)
    at org.springframework.security.providers.anonymous.AnonymousProcessingFilter.doFilterHttp(AnonymousProcessingFilter.java:105)
    at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53)
    at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390)
    at org.springframework.security.ui.rememberme.RememberMeProcessingFilter.doFilterHttp(RememberMeProcessingFilter.java:116)
    at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53)
    at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390)
    at org.springframework.security.wrapper.SecurityContextHolderAwareRequestFilter.doFilterHttp(SecurityContextHolderAwareRequestFilter.java:91)
    at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53)
    at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390)
    at org.springframework.security.ui.basicauth.BasicProcessingFilter.doFilterHttp(BasicProcessingFilter.java:174)
    at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53)
    at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390)
    at org.springframework.security.ui.AbstractProcessingFilter.doFilterHttp(AbstractProcessingFilter.java:278)
    at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53)
    at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390)
    at org.springframework.security.ui.logout.LogoutFilter.doFilterHttp(LogoutFilter.java:89)
    at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53)
    at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390)
    at org.springframework.security.context.HttpSessionContextIntegrationFilter.doFilterHttp(HttpSessionContextIntegrationFilter.java:235)
    at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53)
    at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390)
    at org.springframework.security.util.FilterChainProxy.doFilter(FilterChainProxy.java:175)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:236)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:167)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
    at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:388)
    at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
    at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182)
    at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:765)
    at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:418)
    at org.mortbay.jetty.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:230)
    at org.mortbay.jetty.handler.HandlerCollection.handle(HandlerCollection.java:114)
    at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
    at org.mortbay.jetty.Server.handle(Server.java:326)
    at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:536)
    at org.mortbay.jetty.HttpConnection$RequestHandler.headerComplete(HttpConnection.java:915)
    at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:539)
    at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:212)
    at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:405)
    at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:409)
    at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:582)

Как будто что-то ожидает не просто какой-либо объект аутентификации, а конкретную его реализацию - UsernamePasswordAuthenticationToken.Это заставляет меня думать, что я могу упустить другой пользовательский компонент .. может быть, фильтр?

Ответы [ 2 ]

48 голосов
/ 28 декабря 2011

Если вы реализуете свой собственный AuthenticationProvider, вам не нужно реализовывать UserDetailsService, если вы этого не хотите. UserDetailsService просто предоставляет стандартный DAO для загрузки пользовательской информации, и некоторые другие классы в рамках этой платформы реализованы для ее использования.

Обычно для аутентификации с использованием имени пользователя и пароля вы должны создать экземпляр DaoAuthenticationProvider и ввести его с помощью UserDetailsService. Это все еще может быть вашим лучшим подходом. Если вы реализуете своего собственного провайдера, вы берете на себя ответственность за то, чтобы убедиться, что пользователь ввел правильный пароль и так далее. Однако в некоторых случаях это более простой подход.

Чтобы ответить на ваше "что здесь должно произойти?" комментарий в вашем коде, это будет что-то вроде

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
  UsernamePasswordAuthenticationToken auth = (UsernamePasswordAuthenticationToken) authentication;
  String username = String.valueOf(auth.getPrincipal());
  String password = String.valueOf(auth.getCredentials());

  logger.info("username:" + username);
  logger.info("password:" + password); // Don't log passwords in real app

  // 1. Use the username to load the data for the user, including authorities and password.
  YourUser user = ....

  // 2. Check the passwords match (should use a hashed password here).
  if (!user.getPassword().equals(password)) {
    throw new BadCredentialsException("Bad Credentials");
  }

  // 3. Preferably clear the password in the user object before storing in authentication object
  user.clearPassword();

  // 4. Return an authenticated token, containing user data and authorities  

  return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()) ;
}

Пользовательский объект будет тогда доступен с помощью

Authentication.getPrincipal()

, и вы можете получить доступ к дополнительным свойствам (электронная почта и т. Д.), Приведя его к пользовательской реализации.

Как вы загружаете пользовательские данные, зависит от вас. Все, что заботит Spring Security, - это интерфейс AuthenticationProvider.

Вам также следует хранить хешированные пароли и проверять предоставленный пароль, используя тот же алгоритм, а не простую проверку на равенство.

3 голосов
/ 03 декабря 2013

спасибо за публикацию этого Люка!

Спасло меня от больше повреждения мозга.

Единственное замечание, с которым я столкнулся, для всех, кому небезразлично:

Моя настройка:

  • Grails 2.0.4
  • Groovy 1.8
  • spring-security-core 1.2.7.3
  • spring-security-ui 0.2
  • hibernate 2.0.4

При использовании очень приветствуется упрощенный / элегантный подход, который предлагает Люк, НЕ реализуя пользовательский объект UserDetails (или UserDetailsService)и используя свой собственный объект User domain , который не расширяет ничего особенного, вы должны сделать дополнительный шаг, если используете пользовательские теги "sec" из Spring Security (в вашемстраницы, конечно):

Когда вы создаете базовый, нестандартный UsernamePasswordAuthenticationToken, вы ДОЛЖНЫ передать ему экземпляр чего-то, что расширяет принципал, опять же, если вы хотите, чтобы ваши собственные пружинные теги пробела безопасности работали.Я сделал что-то вроде этого, чтобы сделать его как можно более простым (ссылаясь на значения объекта моего домена пользователя, где это полезно / уместно):

def principalUser = new org.springframework.security.core.userdetails.User(user.username, user.password, user.enabled, !user.accountExpired, !user.passwordExpired,!user.accountLocked, authorities)
def token = new UsernamePasswordAuthenticationToken(principalUser, presentedPassword, authorities)

Это должно удовлетворять условиям, проверенным в grails.plugins.springsecurity.SecurityTagLib..determineSource (), так что, вы знаете, ваши страницы, которые используют <sec:loggedInUserInfo>, будут действительно отображаться:

if (principal.metaClass.respondsTo(principal, 'getDomainClass')) {
            return principal.domainClass
}

В противном случае, если вы создадите экземпляр UsernamePasswordAuthenticationToken с вашим объектом домена пользователя (как Люк показывает в своем примере),этот метод lib тега безопасности (defineSource ()) просто лучше всего выполнит свой уровень и вернет (мета) значение org.codehaus.groovy.grails.commons.DefaultGrailsDomainClass , и вы получите ошибку, когдаВ теге ищется переменная-член username, в которой указывается:

 Error executing tag <sec:ifLoggedIn>: Error executing tag <sec:loggedInUserInfo>: No such property: username for class: org.codehaus.groovy.grails.commons.DefaultGrailsDomainClass

Если не считать повторной реализации / создания подклассов тегов-плагинов Spring-security-core в моем проекте grails, просто нет способа использовать теги-теги и использоватьпользовательский класс вашего домена для создания экземпляра токена, передаваемого из вашего фильтравашему провайдеру.

Опять же, одна дополнительная строка кода - это очень небольшая цена для оплаты:)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...