I have following websocket security configuration:
#Configuration
public class WebSocketAuthorizationSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
#Override
protected void configureInbound(final MessageSecurityMetadataSourceRegistry messages) {
// You can customize your authorization mapping here.
//messages.anyMessage().authenticated();
messages.simpDestMatchers("/hello").hasRole("ADMIN");
}
// TODO: For test purpose (and simplicity) i disabled CSRF, but you should re-enable this and provide a CRSF endpoint.
#Override
protected boolean sameOriginDisabled() {
return true;
}
}
I expect that only admin can send messages into the /hello topic.
and following security configuration:
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static final String SECURE_ADMIN_PASSWORD = "rockandroll";
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.formLogin()
.loginPage("/index.html")
.loginProcessingUrl("/login")
.defaultSuccessUrl("/sender.html")
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/index.html")
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/js/**", "/lib/**", "/images/**", "/css/**", "/index.html", "/","/*.css","/webjars/**", "/*.js").permitAll()
.antMatchers("/websocket").hasRole("ADMIN")
.requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ADMIN")
.anyRequest().authenticated();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new AuthenticationProvider() {
#Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
List<GrantedAuthority> authorities = SECURE_ADMIN_PASSWORD.equals(token.getCredentials()) ?
AuthorityUtils.createAuthorityList("ROLE_ADMIN") : null;
return new UsernamePasswordAuthenticationToken(token.getName(), token.getCredentials(), authorities);
}
});
}
}
Also I have following websocket controller:
#MessageMapping("/hello")
#SendTo("/topic/greetings")
public Greeting greeting(#Payload HelloMessage message, Principal principal) throws Exception {
Thread.sleep(1000); // simulated delay
simpMessagingTemplate.convertAndSendToUser(principal.getName(), "/queue/greetings", new Greeting("Ololo"));
return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!");
}
Next I login as user/pass(It is not admin).
And client successfully sends messages to the /hello topic:
stompClient.send("/app/hello", {}, JSON.stringify({'name': $("#name").val()}));
and method greetinginvokes successfully.
What do I wrong?
It became working after I had provided following definition(added /app):
messages.simpDestMatchers("/app/hello").hasRole("ADMIN");
Related
I am currently working on a spring boot project that has multiple security (authentication) configurations combined:
a rest api with http basic auth
a rest api with jwt auth.
a web (form login) with 2fa auth.
The problem I am experiencing is, that the configurations cannot be entirely seperated. More specific: The authenticiation providers are accumulated (in the provider manager), which prevents my preferred setup from working correctly.
What happens is this: In config (3) i have a custom 2fa authentication provider which checks if the credentials (both password and 2FA code!) are entered correctly. If the password is correct, but the 2fa code is not, it exits (catches) with a (authentication or bad credentials) exception. However, as it exits the 2fa authentication provider, it goes back to the provider manager. The latter has another (DAO) auth provider at hand, and checks the credentials again: but this time: only password! not the 2fa code!). As the password is ok, it authorized the request.
So for short: I am experiencing a problem where the first authentication provider does NOT authorize, but the second does (because that one does not take the 2fa code into account)!
I have been at this for days, and i cannot seem to get this right.
As an alternative i have now opted for a solution using a 2fa custom filter. But is not my preferred solution, as it gives me some frontend problems (i first have to authorize the username/password, and only after that i can check the 2fa code).
Is there a solution using my 2fa auth provider? I would sort of wish that the auth providers would not get accumulated, so that when the first auth. provider exits with bad credentials, the auth procedure ends with a 'bad credentials'.
My config class:
WebSecurityConfig.java
#EnableWebSecurity
#Configuration
#AllArgsConstructor
public class WebSecurityConfig_backup extends WebSecurityConfigurerAdapter {
private UserDetailsService userDetailsService;
private PasswordEncoder passwordEncoder;
private HttpBasicAuthenticationExceptionHandler authenticationExceptionHandler;
private JwtAuthenticationProvider jwtAuthenticationProvider;
private MfaAuthenticationDetailsSource mfaAuthenticationDetailsSource;
private MfaAuthenticationProvider2 mfaAuthenticationProvider;
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Configuration
#Order(1)
public class ApiV1WebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/v1/**")
.httpBasic()
.authenticationEntryPoint(authenticationExceptionHandler)
.and()
.csrf().disable()
.authorizeRequests(authorize -> {
authorize
.mvcMatchers(HttpMethod.GET,
"/api/v1/transaction/**").hasAnyRole("BANK", "ADMIN")
.mvcMatchers(HttpMethod.POST,
"/api/v1/transaction/**").hasAnyRole("BANK", "ADMIN");
//.anyRequest().denyAll();
})
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth .userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
}
#Configuration
#Order(2)
public class ApiV2WebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/v2/**")
.addFilter(bearerTokenAuthenticationFilter())
.addFilter(credentialsAuthenticationFilter())
.csrf().disable()
.authorizeRequests(authorize -> {
authorize
.mvcMatchers(HttpMethod.GET,
"/api/v2/transaction/**").hasAnyRole("BANK", "ADMIN")
.mvcMatchers(HttpMethod.POST,
"/api/v2/transaction/**").hasAnyRole("BANK", "ADMIN");
//.anyRequest().denyAll();
})
.authorizeRequests()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
private BearerTokenAuthenticationFilter bearerTokenAuthenticationFilter() throws Exception {
BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(authenticationManager());
filter.setAuthenticationFailureHandler(authenticationFailureHandler());
return filter;
}
private CredentialsAuthenticationFilter credentialsAuthenticationFilter() throws Exception {
CredentialsAuthenticationFilter filter = new CredentialsAuthenticationFilter(authenticationManager());
filter.setAuthenticationFailureHandler(authenticationFailureHandler());
return filter;
}
private AuthenticationFailureHandler authenticationFailureHandler() {
return (request, response, ex) -> {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType(MediaType.APPLICATION_JSON.toString());
response.setCharacterEncoding(StandardCharsets.UTF_8.displayName());
ResponseWriterUtil.writeErrorResponse(response, ex.getMessage());
};
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(jwtAuthenticationProvider)
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
}
#Configuration
#Order(3)
public class FormWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
//authorisation
http//.addFilterBefore(googleTfaFilter, SessionManagementFilter.class)
.authorizeRequests(authorize -> {
authorize
.mvcMatchers("/", "/login", "/logout", "/registrationPage", "/register").permitAll()
.mvcMatchers(HttpMethod.GET,
"/others1", "/others2").hasAnyRole("USER", "ADMIN")
.mvcMatchers(HttpMethod.POST,
"/others1", "/others2").hasAnyRole("USER", "ADMIN");
})
.formLogin()
.loginPage("/login").permitAll()
.usernameParameter("email")
.loginProcessingUrl("/authenticate")
.defaultSuccessUrl("/")
.failureUrl("/login?error")
.authenticationDetailsSource(mfaAuthenticationDetailsSource)
.and()
.logout()
.deleteCookies("JSESSIONID")
.logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET"))
.logoutSuccessUrl("/login?logout").permitAll()
.and()
.sessionManagement()
.sessionFixation().migrateSession()
.and()
.headers().frameOptions().sameOrigin()
.and()
.csrf();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/js/**", "/img/**", "/lib/**");
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(mfaAuthenticationProvider)
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
}
}
auth provider for config 2:
#Slf4j
#RequiredArgsConstructor
#Component
public class JwtAuthenticationProvider implements AuthenticationProvider {
private final UserDetailsService userDetailsService;
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(BearerTokenAuthenticationToken.class);
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
BearerTokenAuthenticationToken bearerToken = (BearerTokenAuthenticationToken) authentication;
Authentication auth = null;
try {
//validate the token
Jwts.parser().setSigningKey(JWT_TOKEN_SECRET).parseClaimsJws(bearerToken.getToken());
JwtTokenUtil jwtTokenUtil = new JwtTokenUtil(bearerToken.getToken());
String username = jwtTokenUtil.getUsernameFromToken();
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
auth = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
log.debug("Authentication token: " + auth);
} catch (IllegalArgumentException e) {
throw new UserServiceAuthenticationException("Invalid token");
} catch (ExpiredJwtException e) {
throw new UserServiceAuthenticationException("Token expired");
} catch (SignatureException e) {
throw new UserServiceAuthenticationException("Invalid signature");
}
return auth;
}
}
Auth provider for config (3)
#Slf4j
#AllArgsConstructor
#Component
public class MfaAuthenticationProvider2 implements AuthenticationProvider {
private UserRepo userRepository;
private GoogleAuthenticator googleAuthenticator;
private PasswordEncoder passwordEncoder;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
String verficationCode = ((MfaAuthenticationDetails) authentication.getDetails()).getUserMFaCode();
User user = userRepository.findByEmail(authentication.getName()).stream().findFirst().orElse(null);
if(user == null || !passwordEncoder.matches(password, user.getPassword())){
throw new BadCredentialsException("Invalid username or password");
}
try {
if(!googleAuthenticator.authorizeUser(user.getUsername(), Integer.parseInt(verficationCode))){
throw new BadCredentialsException("Invalid verification code.");
}
} catch (Exception e) {
throw new BadCredentialsException("Authentication failed. Please try again.");
}
return new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities());
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
My current alternative solution for config 3:
#Slf4j
#Component
public class GoogleTfaFilter2 extends OncePerRequestFilter {
private final AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
private final GoogleTfaFailureHandler googleTfaFailureHandler = new GoogleTfaFailureHandler();
private final RequestMatcher urlIs2fa = new AntPathRequestMatcher("/verify2fa");
private final RequestMatcher urlIs2fa2 = new AntPathRequestMatcher("/register2fa");
private final RequestMatcher urlResource = new AntPathRequestMatcher("/resources/**");
private final RequestMatcher api = new AntPathRequestMatcher("/api/**");
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
StaticResourceRequest.StaticResourceRequestMatcher staticResourceRequestMatcher =
PathRequest.toStaticResources().atCommonLocations();
if (urlIs2fa.matches(request) || urlResource.matches(request) || urlIs2fa2.matches(request)||
staticResourceRequestMatcher.matcher(request).isMatch() || api.matches(request)) {
filterChain.doFilter(request, response);
return;
}
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && !authenticationTrustResolver.isAnonymous(authentication)){
log.debug("Processing 2FA Filter");
if (authentication.getPrincipal() != null && authentication.getPrincipal() instanceof User) {
User user = (User) authentication.getPrincipal();
if (!user.getMfaPassed()) {
log.debug("2FA Required");
request.getRequestDispatcher("/verify2fa").forward(request, response);
return;
}
}
}
filterChain.doFilter(request, response);
}
}
We are using spring boot 2.2.2.
I want to return the custom error message in the json response along with 401 status code when user enters the wrong credentials for that I did following steps.
WebSecurityConfig.java:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsService userDetailsService;
private final RestAuthenticationEntryPoint restAuthenticationEntryPoint;
public WebSecurityConfig(RepositoryUserDetailsService userDetailsService RestAuthenticationEntryPoint restAuthenticationEntryPoint) {
this.userDetailsService = userDetailsService;
this.restAuthenticationEntryPoint = restAuthenticationEntryPoint;
}
#Override
public void configure(final WebSecurity web) {
web.ignoring()
.antMatchers(HttpMethod.OPTIONS, "/**")
.antMatchers("/proj-name/**/*.{js,html}")
.antMatchers("/test/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and()
.headers()
.frameOptions()
.disable()
.and()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/login").permitAll()
.antMatchers("/error").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic();
}
#Bean
RestAuthenticationEntryPoint authenticationEntryPoint() {
return new RestAuthenticationEntryPoint();
}
#Bean
AuthenticationEntryPoint forbiddenEntryPoint() {
return new HttpStatusEntryPoint(FORBIDDEN);
}
#Bean
public CustomAuthenticationProvider customDaoAuthenticationProvider() throws Exception {
CustomAuthenticationProvider customDaoAuthenticationProvider = new CustomAuthenticationProvider();
customDaoAuthenticationProvider.setPasswordEncoder(new PasswordEncoderConfig().encoder());
customDaoAuthenticationProvider.setUserDetailsService(userDetailsService);
return customDaoAuthenticationProvider;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customDaoAuthenticationProvider());
}
}
RestAuthenticationEntryPoint.java
#Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.addHeader("WWW-Authenticate", "Basic realm=" + "");
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter writer = httpServletResponse.getWriter();
writer.println("HTTP Status 401 - " + e.getMessage());
}
}
CustomAuthenticationProvider.java
public class CustomAuthenticationProvider extends DaoAuthenticationProvider {
#Autowired
private PasswordEncoder passwordEncoder;
#Override
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "test bad credentials"));
}
String presentedPassword = authentication.getCredentials().toString();
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
throw new BadCredentialsException(
messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials","Invalid credentials"));
}
}
}
When I hit the /login api is returning the following response: Full authentication is required to access this resource
If I add .antMatchers("/error").permitAll() is returning the following response.
{
"timestamp": 1594970514264,
"status": 401,
"error": "Unauthorized",
"message": "Unauthorized",
"path": "/login"
}
I tried adding #Component annotation on the CustomAuthenticationProvider then it throws the following error on server startup: java.lang.IllegalArgumentException: A UserDetailsService must be set
I tried adding the CustomAuthenticationFailureHandler by following the url, but didn't work. I've seen couple of articles, but that examples also didn't work. I've also tried creating an exception handler class with #ControllerAdvice annotation still didn't work. Is there any other way to override the default error message or am I missing any annotations or configuration? Thanks in advance for the help!
You could use a controller advice to handle the error thrown by your service :
#ControllerAdvice
#Order(HIGHEST_PRECEDENCE)
public class AppointmentErrorController {
#ExceptionHandler(BadCredentialsException.class)
public ResponseEntity<ApiError> handleError(HttpServletRequest request, BadCredentialsException ex) {
ApiError apiError = new ApiError(...); //retrieve info from BadCredentialsException
return ResponseEntity.status(HttpStatus.UNAUTHORIZED, apiError);
}
I configure oauth2(resource server and auth server) in my spring boot app, but how to perform authentication now? How to use grants which I described in authentication server?
And how to perform autologin when new user has been registered?
#Configuration
public class OAuth2ServerConfig {
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Inject
private Http401UnauthorizedEntryPoint authenticationEntryPoint;
#Inject
private AjaxLogoutSuccessHandler ajaxLogoutSuccessHandler;
#Override
public void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessHandler(ajaxLogoutSuccessHandler)
.and()
.csrf()
.requireCsrfProtectionMatcher(new AntPathRequestMatcher("/authorize"))
.disable()
.headers()
.frameOptions().disable()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/admin").hasAnyAuthority("ADMIN");
}
}
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
private static final String CLIENTID = "app";
private static final String PROP_SECRET = "secret";
private static final Integer TOKEN_VALIDITY_SECONDS = -1;
#Inject
private OAuth2AccessTokenRepository oAuth2AccessTokenRepository;
#Inject
private OAuth2RefreshTokenRepository oAuth2RefreshTokenRepository;
#Bean
public TokenStore tokenStore() {
return new MongoDBTokenStore(oAuth2AccessTokenRepository, oAuth2RefreshTokenRepository);
}
#Inject
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints
.tokenStore(tokenStore())
.authenticationManager(authenticationManager);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient(CLIENTID)
.scopes("read", "write")
.authorities("USER", "ADMIN")
.authorizedGrantTypes("password", "refresh_token")
.secret(PROP_SECRET)
.accessTokenValiditySeconds(TOKEN_VALIDITY_SECONDS);
}
}
}
You should have something like this:
#Component
public class CustomAuthenticationProvider
implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
String name = authentication.getName();
String password = authentication.getCredentials().toString();
if (shouldAuthenticateAgainstThirdPartySystem()) {
// use the credentials
// and authenticate against the third-party system
return new UsernamePasswordAuthenticationToken(
name, password, new ArrayList<>());
} else {
return null;
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(
UsernamePasswordAuthenticationToken.class);
}
}
and register it to your SecurityConfig
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider authProvider;
#Override
protected void configure(
AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated()
.and()
.httpBasic();
}
}
Hi to all Spring Experts!
I have an issue that I'm trying to solve for a while but i think that i have reached a dead end.
So basically what I need, is to configure my Spring-Security (in Spring-Boot) to have two authentication mechanisms (one for Legacy JSP pages and one for REST APIs). So I followed the following post:
multiple authentication mechanisms in a single app using java config
It worked fine with one LDAP authentication provider. But then I tried to extend my LDAP connection to also obtain a ticket from a third party service (that will be used for future connections to other services), and there I had a problem.
So I created a new Authentication Token, Filter and Authentication provider, but the default UsernamePasswordAuthenticationFilter is being fired first, no matter what I do.
I tried to follow this post How to configure a custom filter programatically in Spring Security? and saw that the problem might be in the fact that my filter was extending UsernamePasswordAuthenticationFilter. So I removed this and tried to have a simple AbstractAuthenticationProcessingFilter, still - no luck.
I think the problem is in my WebSecurity configuration. Currently, with the following code I'm gonna share, the REST Api authentication is returning 405 - method not allowed and the legacy Login is stuck in an infinite loop and crashes, even before I hit "Login".
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true) //Enables #PreAuthorize on methods
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private LDAPConfigurationBean ldapBean;
#Autowired
protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
//HERE GOES LDAP CONNECTION STUFF
// Add the custom LDAP + Token provider to the Authentication provider chain
auth.authenticationProvider(new TicketAndLDAPAuthenticationProvider(authenticator,authoritiesPopulator));
// Creating an LDAP provider using the authenticator and the populator.
auth.authenticationProvider(new LdapAuthenticationProvider(authenticator,authoritiesPopulator));
}
#Configuration
#Order(1)
public static class ConfigureFilters extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.addFilterBefore(new TicketAndLDAPAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class);
}
}
//Management Endpoints Authorization
#Configuration
#Order(2)
public static class EndpointsWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/manage/health")
.authorizeRequests()
.anyRequest().permitAll();
}
}
//API Authentication+Authorization
#Configuration
#Order(3)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Autowired
private RestAuthenticationEntryPoint authenticationEntryPoint;
#Autowired
private RestAuthSuccessHandler authSuccessHandler;
#Autowired
private RestAuthFailureHandler authFailureHandler;
#Autowired
private RestLogoutSuccessHandler logoutSuccessHandler;
private String LOGIN_PATH = "/api/authenticate";
private String USERNAME = "username";
private String PASSWORD = "password";
protected void configure(HttpSecurity http) throws Exception {
/*CSRF configuration*/
http.csrf().disable();
http
.antMatcher(LOGIN_PATH)
.authorizeRequests()
.anyRequest().permitAll();
http
.antMatcher("/api/**")
//Stateless session creation - no session will be created or used by Spring Security
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.formLogin().permitAll()
.loginProcessingUrl(LOGIN_PATH)
.usernameParameter(USERNAME)
.passwordParameter(PASSWORD)
.successHandler(authSuccessHandler)
.failureHandler(authFailureHandler)
.and()
.logout().permitAll()
.logoutSuccessHandler(logoutSuccessHandler);
http
.authorizeRequests().anyRequest().authenticated();
}
}
//JSP Authentication+Authorization
#Configuration
#Order(4)
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
/*CSRF configuration*/
http.csrf().disable();
/*Static content*/
http
.authorizeRequests()
.antMatchers("/css*//**").permitAll()
.antMatchers("/images*//**").permitAll()
.antMatchers("/scripts*//**").permitAll()
.antMatchers("/fonts*//**").permitAll()
.antMatchers("/login*").anonymous();
/*Login / Logout configuration*/
http
.formLogin()
.loginPage("/login.htm").permitAll()
.defaultSuccessUrl("/index.htm?name=******")
.failureUrl("/login.htm?error=true")
.and()
.logout().permitAll()
.logoutSuccessUrl("/login.htm")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID");
/*URL roles authorizations*/
http
.authorizeRequests().anyRequest().authenticated();
}
}
}
As you can see, I am trying to configure my filter in the "Configure Filters" method - but I have also tried to configure it inside the adapters, with / without a #Bean annotation - all with no luck.
Filter:
public class TicketAndLDAPAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public TicketAndLDAPAuthenticationFilter() {
super("/*");
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
//Save the password for later
String username = request.getParameter("username");
String password = request.getParameter("password");
TicketAndLDAPAuthenticationToken token = new TicketAndLDAPAuthenticationToken(username,password,null);
return token;
}
}
Edit: forgot to add to the filter:
if ( request.getParameter( "username" ) == null || request.getParameter( "password" ) == null ) == null ) {
return null;
}
Now I get 405 in both login mechanisms.
Token:
public class TicketAndLDAPAuthenticationToken extends UsernamePasswordAuthenticationToken {
private AuthTicket otp;
private String restoredPassword;
public TicketAndLDAPAuthenticationToken( String username, String password, RestAuthLoginTicket otp ) {
super( username, password );
this.otp = otp;
}
public AuthTicket getOTP() {
return otp;
}
public AuthTicket getOtp() {
return otp;
}
public void setOtp(AuthTicket otp) {
this.otp = otp;
}
}
Provider:
public class TicketAndLDAPAuthenticationProvider extends LdapAuthenticationProvider {
#Autowired
TokenUtils tokenUtils;
public TicketAndLDAPAuthenticationProvider(LdapAuthenticator authenticator, LdapAuthoritiesPopulator authoritiesPopulator) {
super(authenticator, authoritiesPopulator);
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
TicketAndLDAPAuthenticationToken token = (TicketAndLDAPAuthenticationToken) super.authenticate(authentication);
token.setOtp(tokenUtils.getTicket(token));
return token;
}
#Override
public boolean supports(Class<?> authentication) {
return TicketAndLDAPAuthenticationToken.class.isAssignableFrom(authentication);
}
}
Thanks in advance!!
So I found the issue(s).
First of all, the right way to configure the authentication managers is not how I configured above because there is no antMatcher and this caused my resources and pages to be open to everybody.
Secondly, the problem that caused the infinite redirects and error 405 was that I haven't defined my filter to accept post.
After fixing that, my JSP login form and authentication mechanism worked fine, but the "/api" was redirecting to the login page instead of the resource.
What brings me to my final point - the http.formLogin() is creating a UsernamePasswordAuthenticationFilter. I have two of them - one for each login. So I had to add http.addFilterBefore() for each one of the logins, but with a different URL.
The "/api" url was again using the default redirects of Spring instead of what I have defined, so I had to override them.
These are the configurations and filters that are working for me:
Security Configuration:
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true) //Enables #PreAuthorize on methods
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private LDAPConfigurationBean ldapBean;
#Autowired
protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
//LDAP Stuff
TicketAndLDAPAuthenticationProvider ticketAndLDAPAuthenticationProvider = new TicketAndLDAPAuthenticationProvider(authenticator,authoritiesPopulator);
auth.authenticationProvider(ticketAndLDAPAuthenticationProvider);
LdapAuthenticationProvider ldapAuthenticationProvider = new LdapAuthenticationProvider(authenticator,authoritiesPopulator);
auth.authenticationProvider(ldapAuthenticationProvider);
}
//Management Endpoints Authorization
#Configuration
#Order(1)
public static class EndpointsWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/manage/health")
.authorizeRequests()
.anyRequest().permitAll();
}
}
//API Authentication+Authorization
#Configuration
#Order(2)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Autowired
private RestAuthenticationEntryPoint authenticationEntryPoint;
#Autowired
private RestAuthSuccessHandler authSuccessHandler;
#Autowired
private RestAuthFailureHandler authFailureHandler;
#Autowired
private RestLogoutSuccessHandler logoutSuccessHandler;
private String LOGIN_PATH = "/api/authenticate";
protected void configure(HttpSecurity http) throws Exception {
/*CSRF configuration*/
http.csrf().disable();
http.addFilterBefore(new TicketAndLDAPAuthenticationFilter(LOGIN_PATH,authSuccessHandler,authFailureHandler), UsernamePasswordAuthenticationFilter.class);
http
.antMatcher("/api/**")
// Stateless session creation - no session will be created or used by Spring Security
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.logout().permitAll()
.logoutSuccessHandler(logoutSuccessHandler);
http
.authorizeRequests().anyRequest().authenticated();
}
}
//JSP Authentication+Authorization
#Configuration
#Order(3)
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
private String LOGIN_PATH = "/login.htm";
#Override
protected void configure(HttpSecurity http) throws Exception {
/*CSRF configuration*/
http.csrf().disable();
http.addFilterBefore(new TicketAndLDAPAuthenticationFilter(LOGIN_PATH), UsernamePasswordAuthenticationFilter.class);
/*Static content*/
http
.authorizeRequests()
.antMatchers("/css*//**").permitAll()
.antMatchers("/images*//**").permitAll()
.antMatchers("/scripts*//**").permitAll()
.antMatchers("/fonts*//**").permitAll()
.antMatchers("/login*").anonymous();
/*Login / Logout configuration*/
http
.formLogin()
.loginPage(LOGIN_PATH).permitAll()
.defaultSuccessUrl("/index.htm?name=******")
.failureUrl("/login.htm?error=true")
.and()
.logout().permitAll()
.logoutSuccessUrl("/login.htm")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID");
/*URL roles authorizations*/
http
.authorizeRequests().anyRequest().authenticated();
}
}
}
And the Filter:
public class TicketAndLDAPAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public TicketAndLDAPAuthenticationFilter(String defaultProcessUrl) {
super(new AntPathRequestMatcher(defaultProcessUrl, "POST"));
}
public TicketAndLDAPAuthenticationFilter(String defaultProcessUrl, AuthenticationSuccessHandler authenticationSuccessHandler, AuthenticationFailureHandler authenticationFailureHandler) {
super(new AntPathRequestMatcher(defaultProcessUrl, "POST"));
setAuthenticationFailureHandler(authenticationFailureHandler);
setAuthenticationSuccessHandler(authenticationSuccessHandler);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
//Save the password for later
String username = request.getParameter("username");
String password = request.getParameter("password");
if ( username==null || password==null) {
return null;
}
TicketAndLDAPAuthenticationToken token = new TicketAndLDAPAuthenticationToken(username,password,null);
return token;
}
}
I want to have a custom Authentication Provider for spring security and i have implemented it like this
#Component
public class ApiCustomAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
System.out.println("ahsgdvjasdhgjasjdh");
return new UsernamePasswordAuthenticationToken("aman", "12345");
}
#Override
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
}
Right now i don't have any logic as i just want to see if spring security is actually using this authentication provider .
i have my security config file as
#Configuration
#EnableWebSecurity
//#ImportResource("classpath:/security/spring_saml_sso_security.xml")
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/*#Autowired
MetadataGeneratorFilter metadataGeneratorFilter;
#Autowired
FilterChainProxy samlFilter;
#Autowired
SAMLEntryPoint samlEntryPoint;
*/
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
try {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/static/**").permitAll()
.antMatchers("/settings/api/**").permitAll()
.antMatchers("/api/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll()
.loginProcessingUrl("/login")
// .usernameParameter("username").passwordParameter("password")
.defaultSuccessUrl("/index",true)
.and()
.httpBasic();
// .defaultSuccessUrl("/", true);
} catch (Exception e) {
// TODO Auto-generated catch block
System.out.println("sadhiasdniaaaaaaaaaaaaaaaa:");
e.printStackTrace();
}
}
#Bean
public ApiCustomAuthenticationProvider apiCustomAuthenticationProvider() {
return new ApiCustomAuthenticationProvider();
}
}
i want to know if this
#Bean
public ApiCustomAuthenticationProvider apiCustomAuthenticationProvider() {
return new ApiCustomAuthenticationProvider();
is the correct way of telling spring security to use the custom authentication manager .
You need to add this in Spring security config:
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new ApiCustomAuthenticationProvider());
}
or
auth.authenticationProvider(apiCustomAuthenticationProvider())
And as a reminder, if you return token :
UsernamePasswordAuthenticationToken("aman", "12345"),
spring will not give authorization to user. Instead you need to assign role :
List<GrantedAuthority> grantedAuths = new ArrayList<GrantedAuthority>();
grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
UsernamePasswordAuthenticationToken("aman", "12345",grantedAuths) ;
As stated above,you are giving user ROLE_USER and then user can use all authenticated page.
Hope its help.