Статьи

Grails Custom AuthenticationProvider

Чтобы повысить безопасность в нашем новом приложении Grails, я приступил к реализации Spring Security Plugin . Начать работу со стандартным сценарием имени пользователя и пароля было несложно, поскольку все это автоматически подключалось плагином. Это решило половину моей проблемы, но мы также должны поддерживать аутентификацию с помощью SAML, и не было четких примеров того, как это сделать. Я хотел бы поделиться тем, что я построил на случай, если у кого-то есть подобное требование. Я не буду фокусироваться на особенностях SAML, а скорее на том, как создать любой пользовательский поставщик аутентификации в Grails.
Вы можете сопоставить URL-адрес с фильтром, расширив AbstractAuthenticationProcessingFilter и зарегистрировав его в Spring. Затем вы можете предоставить этот URL для пользовательской аутентификации. В моем случае это выглядело примерно так:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
class SamlAuthenticationFilter extends AbstractAuthenticationProcessingFilter  {
 
    public SamlAuthenticationFilter() {
        super("/somecustomauth")
    }
 
    @Override
    Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod())
        }
 
        String accessToken = request.getParameter("sometoken")
        return this.getAuthenticationManager().authenticate(new SamlAuthenticationToken(accessToken));
    }
 
}

Затем фильтр устанавливается как Spring bean вместе с поставщиком аутентификации, о котором я расскажу в ближайшее время:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import SamlAuthenticationFilter
import SamlAuthenticationProvider
 
beans = {
    samlAuthenticationFilter(SamlAuthenticationFilter) {
        authenticationManager = ref('authenticationManager')
        sessionAuthenticationStrategy = ref('sessionAuthenticationStrategy')
        authenticationSuccessHandler = ref('authenticationSuccessHandler')
        authenticationFailureHandler = ref('authenticationFailureHandler')
        rememberMeServices = ref('rememberMeServices')
        authenticationDetailsSource = ref('authenticationDetailsSource')
    }
 
    samlAuthenticationProvider(SamlAuthenticationProvider) {
        sAMLAuthenticationService = ref('SAMLAuthenticationService')
        sAMLSettingsService = ref('SAMLSettingsService')
        userDetailsService = ref('userDetailsService')
        passwordEncoder = ref('passwordEncoder')
        userCache = ref('userCache')
        saltSource = ref('saltSource')
        preAuthenticationChecks = ref('preAuthenticationChecks')
        postAuthenticationChecks = ref('postAuthenticationChecks')
    }
}

И тогда боб регистрируется как фильтр в Bootstrap:

01
02
03
04
05
06
07
08
09
10
11
12
import org.codehaus.groovy.grails.plugins.springsecurity.SecurityFilterPosition
import org.codehaus.groovy.grails.plugins.springsecurity.SpringSecurityUtils
 
class BootStrap {
 
    def init = { servletContext ->
        SpringSecurityUtils.clientRegisterFilter('samlAuthenticationFilter', SecurityFilterPosition.SECURITY_CONTEXT_FILTER.order + 10)
    }
 
    def destroy = {
    }
}

Нам также необходимо создать класс Token, который будет использоваться фильтром и поставщиком аутентификации:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.userdetails.UserDetails
 
class SamlAuthenticationToken extends UsernamePasswordAuthenticationToken {
 
    String token
 
    public SamlAuthenticationToken(String token) {
        super(null, null);
        this.token = token;
    }
 
    public SamlAuthenticationToken(UserDetails principal, String samlResponse) {
        super(principal, samlResponse, principal.getAuthorities())
    }
 
}

И, наконец, сам AuthenticationProvider:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import org.springframework.security.authentication.dao.DaoAuthenticationProvider
import org.springframework.security.core.Authentication
import sonicg.authentication.SAMLAuthenticationService
 
class SamlAuthenticationProvider extends DaoAuthenticationProvider {
 
    @Override
    Authentication authenticate(Authentication authentication) {
        def token = (SamlAuthenticationToken) authentication
 
        def user = // define user if credentials check out
 
        if (user){
            def userDetails = userDetailsService.loadUserByUsername(user.username)
            def token1 = new SamlAuthenticationToken(userDetails, token.samlResponse)
            return token1
        }else{
            return null
        }
 
    }
 
    @Override
    public boolean supports(Class authentication) {
        return (SamlAuthenticationToken.class.isAssignableFrom(authentication));
    }
}

Последний кусочек головоломки состоит в том, чтобы сказать Spring, чтобы он попытался использовать этот поставщик аутентификации перед другими стандартными тремя в Config.groovy:

1
2
3
4
5
grails.plugins.springsecurity.providerNames = [
        'samlAuthenticationProvider',
        'daoAuthenticationProvider',
        'anonymousAuthenticationProvider',
        'rememberMeAuthenticationProvider']
В этом случае важно, чтобы пользовательский фильтр шел первым, поскольку его токен является подклассом UsernamePasswordAuthenticationToken. Если поставщик DAO был первым, он попытался бы аутентифицировать пользовательский токен, прежде чем наш фильтр получит шанс.
Это оно! Надеюсь, это окажется полезным для кого-то. Это также только первый черновик, и, возможно, когда требования безопасности будут разработаны, я смогу усовершенствовать реализацию и поделиться тем, что я узнал.

Ссылка: Пользовательский AuthenticationProvider с Grails от нашего партнера JCG Кали Каллина во время путешествия Каллина Нагелберга в западный блог.