java - gui - intellij idea show form
Múltiples mecanismos de autenticación en una sola aplicación usando Java Config (4)
Actualmente tengo un único mecanismo de autenticación en mi aplicación que es usar LDAP para autenticación y autorización. Mi configuración de seguridad se ve así
@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.httpBasic();
}
@Configuration
protected static class AuthenticationConfiguration extends GlobalAuthenticationConfigurerAdapter {
@Value("${ldap-${env}.manager.dn}")
private String managerDn;
@Value("${ldap-${env}.manager.pass}")
private String managerPass;
@Value("${ldap-${env}.server.url}")
private String url;
@Value("${ldap.password.attribute:userPassword}")
private String passwordAttr;
@Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication().userDnPatterns("uid={0},ou=people").groupSearchBase("ou=groups")
.groupSearchFilter("(member={0})").userSearchBase("ou=people").userSearchFilter("(uid={0})")
.userDetailsContextMapper(new CustomLdapPersonContextMapper())
// .passwordCompare()
// .passwordAttribute(passwordAttr)
// .passwordEncoder(new PlaintextPasswordEncoder())
// .and()
.contextSource().managerDn(managerDn).managerPassword(managerPass).url(url);
}
}
}
Sin embargo, hay situaciones en las que los usuarios pueden ingresar con un token de sesión que puede autenticarse desde un servidor de claves de sesión y un token válido devuelve un nombre de usuario que luego puede usarse para cargar información de autorización de LDAP para ese usuario. Entonces, mi segundo mecanismo de autenticación debería ocurrir primero, si un token de sesión está presente en los encabezados http, debe realizar la autenticación del token y luego la búsqueda de ldap, y si no hay un token de sesión, debe caer al mecanismo de autenticación actual. ¿Cómo puedo agregar esta segunda capa de autenticación?
La respuesta aceptada tiene el problema de que la solicitud actual no se otorga, es decir. ¡solo para las siguientes solicitudes se establece la sesión! Por lo tanto, necesitaba configurar en el punto 2
public class MyAuthorizationFilter extends AbstractAuthenticationProcessingFilter {
public MyAuthorizationFilter() {
super( "/*" ); // allow any request to contain an authorization header
}
public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response ) throws AuthenticationException
{
if ( request.getHeader( "Authorization" ) == null ) {
return null; // no header found, continue on to other security filters
}
// required to use the token
myNewToken = new MyAuthorizationToken( request.getHeader( "Authorization" ) );
// and set in the current context ==> the current request is as well authorized
SecurityContextHolder.getContext().setAuthentication(myNewToken);
// return a new authentication token to be processed by the authentication provider
return myNewToken;
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
// try to authenticate the current request
attemptAuthentication((HttpServletRequest) req, (HttpServletResponse) res);
super.doFilter(req, res, chain);
}
}
de lo contrario, la solicitud actual aún no se autentica, ¡aunque ya se ha creado una sesión! (Y los proveedores que no necesito, es decir, agregar filtro es suficiente).
Otra opción para agregar un segundo proveedor de autenticación: simplemente especifique otro en
AuthenticationManagerBuilder
.
Debido a que la anotación
@EnableWebSecurity
está anotada con
EnableGlobalAuthentication
, puede configurar la instancia global de
AuthenticationManagerBuilder
.
(Vea los
javadocs
para más detalles).
Por ejemplo, aquí tenemos un proveedor de autenticación LDAP, así como un proveedor de autenticación en memoria (codificado) (esto es algo que hacemos en el desarrollo para que los usuarios locales prueben):
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Value("${user.role}")
private String userRole; // i.e. ROLE_APP_USER
@Value("${include.test.users}")
private boolean includeTestUsers;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/**/js/**").permitAll()
.antMatchers("/**/images/**").permitAll()
.antMatchers("/**/favicon.ico").permitAll()
.antMatchers("/**/css/**").permitAll()
.antMatchers("/**/fonts/**").permitAll()
.antMatchers("/**").hasAnyRole(userRole)
.and().formLogin().loginPage("/login").permitAll().and().logout().permitAll();
http.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout"));
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth, LdapContextSource contextSource) throws Exception {
auth.ldapAuthentication()
.userSearchBase("OU=Users OU")
.userSearchFilter("sAMAccountName={0}")
.groupSearchBase("OU=Groups OU")
.groupSearchFilter("member={0}")
.contextSource(contextSource);
if (includeTestUsers) {
auth.inMemoryAuthentication().withUser("user").password("u").authorities(userRole);
}
}
}
Pasé bastante tiempo dando vueltas a la seguridad de primavera cuando utilicé la configuración pura de Java. Hay algunos pasos involucrados para que esto funcione. Debería ser algo en este sentido. El proceso básico es el siguiente:
-
Cree filtros personalizados para verificar las solicitudes de información de autorización específica
-
Cada filtro devuelve un valor nulo (si no se encuentra ninguna autorización de ese tipo) o un AbstractAuthenticationToken personalizado
-
Si un filtro devuelve un token, cada método de soporte (clase) de AuthenticationProvider se invocará con ese token que devuelve verdadero | falso si intenta probar la autenticación
-
intentAuthentication se llamará en el AuthenticationProvider que admite el token. Aquí realiza cualquier llamada de servicio para autenticar al usuario. A continuación, puede lanzar LoginException o llamar a audit.setAuthenticated (true) y devolver el token para una autenticación exitosa.
He estado usando esta configuración durante un tiempo que admite varios métodos de autenticación (solicitud firmada, nombre de usuario / contraseña, oauth, etc.) y funciona bastante bien.
También puede pasar AuthenticationSuccessHandler''s y AuthenticationFailuersHandler''s a los filtros de seguridad personalizados para proporcionar estrategias de redireccionamiento personalizadas y manejo de fallas.
También asegúrese de configurar los emparejadores de hormigas en los constructores del filtro para controlar qué patrones de URL también aplican los filtros. Por ejemplo, un filtro de solicitud ldap probablemente debería verificarse con cualquier solicitud "/ *", mientras que un filtro de nombre de usuario / contraseña solo se puede verificar en POST para / iniciar sesión o algo similar.
Código de ejemplo:
1) Cree los AuthenticationToken personalizados para cada tipo de autenticación que desee admitir
public class LDAPAuthorizationToken extends AbstractAuthenticationToken {
private String token;
public LDAPAuthorizationToken( String token ) {
super( null );
this.token = token;
}
public Object getCredentials() {
return token;
}
public Object getPrincipal() {
return null;
}
}
public class OTPAuthorizationToken extends UsernamePasswordAuthenticationToken {
private String otp;
public OTPAuthorizationToken( String username, String password, String otp ) {
super( username, password );
this.otp = otp;
}
public String getOTP() {
return otp;
}
}
2) Crear filtros de seguridad personalizados para cada tipo
public class LDAPAuthorizationFilter extends AbstractAuthenticationProcessingFilter {
@Autowired
private UserDetailsService userDetailsService;
public LDAPAuthorizationFilter() {
super( "/*" ); // allow any request to contain an authorization header
}
public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response ) throws AuthenticationException
{
if ( request.getHeader( "Authorization" ) == null ) {
return null; // no header found, continue on to other security filters
}
// return a new authentication token to be processed by the authentication provider
return new LDAPAuthorizationToken( request.getHeader( "Authorization" ) );
}
}
public class OTPAuthorizationFilter extends AbstractAuthenticationProcessingFilter {
@Autowired
private UserDetailsService userDetailsService;
public OTPAuthorizationFilter() {
super( "/otp_login" );
}
public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response ) throws AuthenticationException
{
if ( request.getParameter( "username" ) == null || request.getParameter( "password" ) == null || request.getParameter( "otp" ) == null ) {
return null;
}
// return a new authentication token to be processed by the authentication provider
return new OTPAuthorizationToken( request.getParameter( "username" ), request.getParameter( "password" ), request.getParameter( "otp" ) );
}
}
3) Crear proveedores de autenticación personalizados
public class LDAPAuthenticationProvider implements AuthenticationProvider {
@Autowired
private MyAuthenticationService sampleService;
@Override
public Authentication authenticate( Authentication authentication ) throws AuthenticationException {
LDAPAuthorizationToken auth = (LDAPAuthorizationToken)authentication;
String username = sampleService.verifyToken( auth.getCredentials() );
if ( username == null ) {
throw new LoginException( "Invalid Token" );
}
auth.setAuthenticated( true );
return auth;
}
@Override
public boolean supports( Class<?> authentication ) {
if ( authentication.isAssignableFrom( LDAPAuthorizationToken.class ) ) {
return true;
}
return false;
}
}
public class OTPAuthenticationProvider implements AuthenticationProvider {
@Autowired
private MyAuthenticationService sampleService;
@Override
public Authentication authenticate( Authentication authentication ) throws AuthenticationException {
OTPAuthorizationToken auth = (OTPAuthorizationToken)authentication;
String error = sampleService.loginWithOTP( auth.getPrincipal(), auth.getCredentials(), auth.getOTP() );
if ( error != null ) {
throw new LoginException( error );
}
auth.setAuthenticated( true );
return auth;
}
@Override
public boolean supports( Class<?> authentication ) {
if ( authentication.isAssignableFrom( OTPAuthorizationToken.class ) ) {
return true;
}
return false;
}
}
4) Configurar la seguridad de primavera
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure( HttpSecurity http ) throws Exception {
// configure filters
http.addFilterBefore( new LDAPAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class );
http.addFilterBefore( new OTPAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class );
// configure authentication providers
http.authenticationProvider( new LDAPAuthenticationProvider() );
http.authenticationProvider( new OTPAuthenticationProvider() );
// disable csrf
http.csrf().disable();
// setup security
http.authorizeRequests()
.anyRequest()
.fullyAuthenticated()
.and().httpBasic();
}
}
¡Espero que ayude!
Solo quiero agregar a la respuesta de mclema. Es posible que deba agregar la anulación para una autenticación exitosa y continuar la cadena de filtros o de lo contrario el usuario será redirigido a la URL predeterminada ("/") en lugar de la original (por ejemplo: / myrest / server / somemethod)
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authResult);
SecurityContextHolder.setContext(context);
chain.doFilter(request, response);
}