Je dois dire que je suis très confus à propos de l'ensemble du modèle et j'ai besoin d'aide pour coller toutes les pièces flottantes ensemble.
Je ne fais pas Spring REST, juste des contrôleurs WebMVC simples.
Ma mission: je veux un formulaire de connexion avec un nom d'utilisateur + authentification par passe. Je souhaite m'authentifier auprès d'un service tiers. En cas de succès, je veux retourner un cookie mais PAS utiliser le mécanisme de jeton de cookie par défaut. Je veux que le cookie ait un jeton JWT à la place. En tirant parti du mécanisme des cookies, chaque demande sera envoyée avec le JWT.
Donc, pour le décomposer, j'ai les modules suivants à prendre en charge:
remplacer le jeton de session de cookie par mon implémentation personnalisée en cas d'authentification réussie
à chaque demande, analyser le JWT à partir du cookie (en utilisant un filtre)
extraire les détails/données utilisateur du JWT pour être accessible aux contrôleurs
Qu'est-ce qui prête à confusion? (veuillez me corriger là où je me trompe)
Authentification tierce
pour m'authentifier contre un tiers, je devrai avoir un fournisseur personnalisé en étendant AuthenticationProvider
public class JWTTokenAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate( Authentication authentication ) throws AuthenticationException {
// auth against 3rd party
// return Authentication
return new UsernamePasswordAuthenticationToken( name, password, new ArrayList<>() );
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals( UsernamePasswordAuthenticationToken.class );
}
}
Des questions:
remplacer le jeton de cookie par un JWT
Je ne sais pas comment faire cela avec élégance, je peux penser à un certain nombre de façons, mais pas à la sécurité Spring et je ne veux pas sortir du flux. Serait reconnaissant pour toutes suggestions ici!
analyser le JWT à chaque demande d'un cookie
D'après ce que je comprends, j'ai besoin d'étendre AbstractAuthenticationProcessingFilter comme ça
public class CookieAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
@Override
public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response )
throws AuthenticationException, IOException, ServletException {
String token = "";
// get token from a Cookie
// create an instance to Authentication
TokenAuthentication authentication = new TokenAuthentication(null, null);
return getAuthenticationManager().authenticate(tokenAuthentication);
}
@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
super.doFilter(req, res, chain);
}
}
Des questions:
Il semble que j'ai toutes les pièces dont j'ai besoin, sauf le remplacement de la session de cookie, mais je ne peux pas les mettre dans un seul modèle cohérent et j'ai besoin de quelqu'un qui comprend assez bien la mécanique pour que je puisse coller tout cela dans un seul module .
MISE À JOUR 1
OK, je pense que j'arrive là où cela commence ... https://github.com/spring-projects/spring-security/blob/master/web/src/main/Java/org/springframework/ security/web/authentication/UsernamePasswordAuthenticationFilter.Java
Ce filtre s'enregistre auprès de POST -> "/ login" et crée une instance de UsernamePasswordAuthenticationToken et passe le contrôle au filtre suivant.
La question est de savoir où la session de cookie est définie ....
MISE À JOUR 2
Cette section du dos donne le flux de niveau supérieur qui me manquait, pour celui qui passe par là, regardez ici ... http://docs.spring.io/spring-security/site/docs/ current/reference/htmlsingle/# tech-intro-authentication
Cette section concernant AuthenticationProvider ... http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#core-services-authentication-manager
MISE À JOUR 3 - cas de travail, est-ce le meilleur moyen ??
Donc, après avoir fouillé dans les documents de Spring Security et leurs sources, j'ai réussi à faire fonctionner le modèle initial. Maintenant, en faisant cela, j'ai réalisé qu'il y avait plus d'une façon de le faire. Un conseil pour savoir pourquoi choisir de cette façon VS ce que Denys a proposé ci-dessous?
Exemple de travail ci-dessous ...
Pour que cela fonctionne comme décrit dans le message d'origine, voici ce qui doit se produire ...
Filtre personnalisé
public class CookieAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public CookieAuthenticationFilter( RequestMatcher requestMatcher ) {
super( requestMatcher );
setAuthenticationManager( super.getAuthenticationManager() );
}
@Override
public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response )
throws AuthenticationException, IOException, ServletException {
String token = "";
// get token from a Cookie
Cookie[] cookies = request.getCookies();
if( cookies == null || cookies.length < 1 ) {
throw new AuthenticationServiceException( "Invalid Token" );
}
Cookie sessionCookie = null;
for( Cookie cookie : cookies ) {
if( ( "someSessionId" ).equals( cookie.getName() ) ) {
sessionCookie = cookie;
break;
}
}
// TODO: move the cookie validation into a private method
if( sessionCookie == null || StringUtils.isEmpty( sessionCookie.getValue() ) ) {
throw new AuthenticationServiceException( "Invalid Token" );
}
JWTAuthenticationToken jwtAuthentication = new JWTAuthenticationToken( sessionCookie.getValue(), null, null );
return jwtAuthentication;
}
@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
super.doFilter(req, res, chain);
}
}
Fournisseur d'authentification
attachez le fournisseur au jeton UsernamePasswordAuthentication qui est généré par UsernamePasswordAuthenticationFilter, qui s'attache au POST "/ login". Pour formlogin avec POST to "/ login" générera UsernamePasswordAuthenticationToken et votre fournisseur sera déclenché
@Component
public class ApiAuthenticationProvider implements AuthenticationProvider {
@Autowired
TokenAuthenticationService tokenAuthService;
@Override
public Authentication authenticate( Authentication authentication ) throws AuthenticationException {
String login = authentication.getName();
String password = authentication.getCredentials().toString();
// perform API call to auth against a 3rd party
// get User data
User user = new User();
// create a JWT token
String jwtToken = "some-token-123"
return new JWTAuthenticationToken( jwtToken, user, new ArrayList<>() );
}
@Override
public boolean supports( Class<?> authentication ) {
return authentication.equals( UsernamePasswordAuthenticationToken.class );
}
}
objet d'authentification personnalisé
Pour le JWT, nous voulons avoir notre propre objet jeton d'authentification pour transporter les données que nous voulons le long de la pile.
public class JWTAuthenticationToken extends AbstractAuthenticationToken {
User principal;
String token;
public JWTAuthenticationToken( String token, User principal, Collection<? extends GrantedAuthority> authorities ) {
super( authorities );
this.token = token;
this.principal = principal;
}
@Override
public Object getCredentials() {
return null;
}
@Override
public Object getPrincipal() {
return principal;
}
public void setToken( String token ) {
this.token = token;
}
public String getToken() {
return token;
}
}
Gestionnaire de réussite d'authentification
Cela est appelé lorsque notre fournisseur personnalisé a fait son travail en authentifiant l'utilisateur par rapport à un tiers et en générant un jeton JWT.C'est l'endroit où le cookie entre dans la réponse.
@Component
public class AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
if( !(authentication instanceof JWTAuthenticationToken) ) {
return;
}
JWTAuthenticationToken jwtAuthenticaton = (JWTAuthenticationToken) authentication;
// Add a session cookie
Cookie sessionCookie = new Cookie( "someSessionId", jwtAuthenticaton.getToken() );
response.addCookie( sessionCookie );
//clearAuthenticationAttributes(request);
// call the original impl
super.onAuthenticationSuccess( request, response, authentication );
}
}
Accrocher tout cela ensemble
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired @Required
ApiAuthenticationProvider apiAuthProvider;
@Autowired @Required
AuthenticationSuccessHandler authSuccessHandler;
@Autowired @Required
SimpleUrlAuthenticationFailureHandler authFailureHandler;
@Override
protected void configure( AuthenticationManagerBuilder auth ) throws Exception {
auth.authenticationProvider( apiAuthProvider );
}
@Override
protected void configure( HttpSecurity httpSecurity ) throws Exception {
httpSecurity
// don't create session
.sessionManagement()
.sessionCreationPolicy( SessionCreationPolicy.STATELESS )
.and()
.authorizeRequests()
.antMatchers( "/", "/login", "/register" ).permitAll()
.antMatchers( "/js/**", "/css/**", "/img/**" ).permitAll()
.anyRequest().authenticated()
.and()
// login
.formLogin()
.failureHandler( authFailureHandler )
//.failureUrl( "/login" )
.loginPage("/login")
.successHandler( authSuccessHandler )
.and()
// JWT cookie filter
.addFilterAfter( getCookieAuthenticationFilter(
new AndRequestMatcher( new AntPathRequestMatcher( "/account" ) )
) , UsernamePasswordAuthenticationFilter.class );
}
@Bean
SimpleUrlAuthenticationFailureHandler getAuthFailureHandler() {
SimpleUrlAuthenticationFailureHandler handler = new SimpleUrlAuthenticationFailureHandler( "/login" );
handler.setDefaultFailureUrl( "/login" );
//handler.setUseForward( true );
return handler;
}
CookieAuthenticationFilter getCookieAuthenticationFilter( RequestMatcher requestMatcher ) {
CookieAuthenticationFilter filter = new CookieAuthenticationFilter( requestMatcher );
filter.setAuthenticationFailureHandler( authFailureHandler );
return filter;
}
}
La manière la plus simple consiste à ajouter Spring Session dans votre projet et à étendre HttpSessionStrategy qui fournit des hooks pratiques pour les événements créés/détruits de session et dispose d'une méthode pour extraire la session de HttpServletRequest.