Чтобы повысить безопасность в нашем новом приложении 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 Кали Каллина во время путешествия Каллина Нагелберга в западный блог.