Spring Security OAuth2 SSO with Custom provider + logout - spring-security

I'm trying to implement sso with Spring Security Oauth2 using Spring-boot and Dave Syer samples
I want to use my custom server provider and it's working fine.
For the client, I want user to be authenticate (so redirected to OAuth2 url) when they try to access client site (eg localhost:8080/) and redirect back to index.html file once authenticated. I also want to implement logout when user on a link in index.html file.
I've come up with this following client sso client:
package org.ikane;
import java.io.IOException;
import java.security.Principal;
import java.util.Arrays;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.WebUtils;
#SpringBootApplication
#Controller
public class DemoSsoOauth2ClientApplication implements CommandLineRunner {
private static final Logger logger = LoggerFactory.getLogger(DemoSsoOauth2ClientApplication.class);
#Override
public void run(String... arg0) throws Exception {
SecurityContext securityContext = SecurityContextHolder.getContext();
try {
Authentication authentication = securityContext.getAuthentication();
logger.info(authentication.getDetails().toString());
SecurityContextHolder.clearContext();
} catch (Exception e) {
logger.error("Error", e);
}
}
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(DemoSsoOauth2ClientApplication.class, args);
ConfigurableEnvironment env = applicationContext.getEnvironment();
logger.info("\n\thttp://localhost:{}{}\n\tProfiles:{}\n",
StringUtils.defaultIfEmpty(env.getProperty("server.port"), "8080"),
StringUtils.defaultIfEmpty(env.getProperty("server.contextPath"), "/"),
Arrays.toString(env.getActiveProfiles()));
}
#RequestMapping(value="/")
public String home() {
return "index";
}
#RequestMapping(value="/user")
#ResponseBody
public Principal user(Principal user) {
return user;
}
/**
* The Class OAuthConfiguration that sets up the OAuth2 single sign on
* configuration and the web security associated with it.
*/
#Component
#Controller
#EnableOAuth2Sso
protected static class OAuthClientConfiguration extends WebSecurityConfigurerAdapter {
private static final String CSRF_COOKIE_NAME = "XSRF-TOKEN";
private static final String CSRF_ANGULAR_HEADER_NAME = "X-XSRF-TOKEN";
#Override
public void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**").authorizeRequests()
.antMatchers("/index.html", "/").permitAll().anyRequest()
.authenticated().and().csrf().csrfTokenRepository(csrfTokenRepository())
.and().addFilterAfter(csrfHeaderFilter(), CsrfFilter.class);
}
private Filter csrfHeaderFilter() {
return new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, CSRF_COOKIE_NAME);
String token = csrf.getToken();
if (cookie == null || token != null
&& !token.equals(cookie.getValue())) {
cookie = new Cookie(CSRF_COOKIE_NAME, token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
};
}
/**
* Angular sends the CSRF token in a custom header named "X-XSRF-TOKEN"
* rather than the default "X-CSRF-TOKEN" that Spring security expects.
* Hence we are now telling Spring security to expect the token in the
* "X-XSRF-TOKEN" header.
*
* This customization is added to the csrf() filter.
*
* #return
*/
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName(CSRF_ANGULAR_HEADER_NAME);
return repository;
}
}
}
You can find a GitHub source. Any hints on how to implement this use case?
Thanks in advance

To make your client app redirect to the Authorization Server just add
the annotation #EnableOAuth2Sso on your WebSecurityConfigurerAdapter and
place the proper OAuth2 configurations (client-id, secret, access token uri...) in your properties file.
(I'm assuming that your client app is using Spring Boot as well)
To end the user's session you have to redirect to an endpoint in the authorization server and logout programmatically as shown in this post.
I have created a repository on github with a sample app that has those features that you are looking for.
Please check it out and let me know if it helps you.

Related

Successful login response with a 404

Currently, I learning security and I'm trying to make a simple login for my Android app. I'm using a custom Filter to request access to my protected data. This way I don't need the default login-page, and use a custom login in my android app instead. But when I do a successful login, I get a 404 response instead of a 200. I there a way to change this as it is set up right now?
MAIN.class
package com.ssl.Oauth2;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}
Controller.class
#RestController
public class Controller {
#RequestMapping("/protected-resource")
public String protectedResource() {
return "The Protected Resource";
}
#RequestMapping("/public-resource")
public String publicResource() {
return "The Public Resource";
}
}
SecurityConfiguration.class
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("john").password("{noop}12345").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
CustomFilter mupaf = new CustomFilter();
mupaf.setAuthenticationManager(authenticationManager());
http
.csrf().disable()
.addFilterAt(
mupaf,
UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/protected-resource").authenticated()
.antMatchers(HttpMethod.POST, "/login").permitAll();
}
}
CustomFilter
package com.ssl.Oauth2;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
public class CustomFilter extends AbstractAuthenticationProcessingFilter {
protected CustomFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
//
}
#Override
public Authentication attemptAuthentication(
HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String username, password;
// Credentials to Java Map
try {
Map<String, String> requestMap = new ObjectMapper().readValue(request.getInputStream(), Map.class);
username = requestMap.get("username");
password = requestMap.get("password");
} catch (IOException e) {
throw new AuthenticationServiceException(e.getMessage(), e);
}
// Token
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// TEST
System.out.println("\n------------------------------------------------\n");
System.out.println("AUTHENTICATION REQUEST:\n" +this.getAuthenticationManager().authenticate(authRequest) );
System.out.println("\n------------------------------------------------\n");
// the method authenticate of the AuthenticationManager receives a token
return this.getAuthenticationManager().authenticate(authRequest);
}
}

Getting exception spring security User account is locked\

We have implemented Spring Security in our angular spring boot project.
Here we are getting exception spring security User account is locked
Please review the following code.
SecurityConfiguration.java
package com.jwt.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.csrf.CsrfFilter;
import com.jwt.security.filter.AuthenticationTokenFilter;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration<jwtAuthenticationEntryPoint> extends WebSecurityConfigurerAdapter{
#Autowired private UserDetailsService userDetailsService;
#Autowired private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint ;
#Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(this.userDetailsService).passwordEncoder( PasswordEncoder());
}
#Bean
public PasswordEncoder PasswordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public AuthenticationTokenFilter authenticationTokenFilterBean( ) {
return new AuthenticationTokenFilter();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers("/**").permitAll()
.antMatchers("/registration").permitAll()
.antMatchers("/login").permitAll()
.antMatchers(HttpMethod.OPTIONS ,"/**").permitAll()
.anyRequest().authenticated();
httpSecurity.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class);
httpSecurity.headers().cacheControl();
httpSecurity.headers().httpStrictTransportSecurity().includeSubDomains(true).maxAgeInSeconds(31536000);
}
}
the authentication token filter AuthenticationTokenFilter.hjava
package com.jwt.security.filter;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;
import com.jwt.security.JwtTokenUtil;
public class AuthenticationTokenFilter extends OncePerRequestFilter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Value("${jwt.header}")
private String tokenHeader;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// TODO Auto-generated method stub
String authToken = request.getHeader(this.tokenHeader);
if (authToken != null && authToken.length() > 7) {
authToken = authToken.substring(7);
}
String username = jwtTokenUtil.getUsernameFromToken(authToken);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
boolean isValid = jwtTokenUtil.validateToken(authToken, userDetails);
if (isValid) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
filterChain.doFilter(request, response);
}
}
Here i am getting null for authToken when running from postman
The code for JwtUtil is as following
package com.jwt.security;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
#Component
public class JwtTokenUtil implements Serializable {
static final String CLAIM_KEY_USERNAME = "sub";
static final String CLAIM_KEY_AUDIENCE = "audience";
static final String CLAIM_KEY_CREATED = "created";
#Value("${jwt.secret}")
private String secret;
#Value("${jwt.expiration}")
private Long expiration;
public String getUsernameFromToken(String authToken) {
String username = null;
try {
final Claims claims = getClaimsFromToken(authToken);
username = claims.getSubject();
} catch (Exception e) {
// TODO Auto-generated catch block
username = null;
}
return username;
}
private Claims getClaimsFromToken(String authToken) {
// TODO Auto-generated method stub
Claims claims = null;
try {
claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(authToken).getBody();
} catch (Exception e) {
// TODO Auto-generated catch block
claims = null;
}
return claims;
}
public boolean validateToken(String authToken, UserDetails userDetails) {
// TODO Auto-generated method stub
JwtUser user = (JwtUser) userDetails;
final String username = getUsernameFromToken(authToken);
return (username.equals(user.getUsername()) && !isTokenExpired(authToken));
}
private boolean isTokenExpired(String authToken) {
final Date expiration = getExpirationDateFromToken(authToken);
return expiration.before(new Date());
}
private Date getExpirationDateFromToken(String authToken) {
// TODO Auto-generated method stub
Date expiration = null;
final Claims claims = getClaimsFromToken(authToken);
if (claims != null) {
expiration = claims.getExpiration();
} else {
expiration = null;
}
return expiration;
}
public String generateToken(JwtUser userDetails) {
Map<String,Object> claims = new HashMap<String,Object>();
claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
claims.put(CLAIM_KEY_CREATED, new Date());
return generateToken(claims);
}
public String generateToken(Map<String , Object> claims ) {
return Jwts.builder().setClaims(claims).setExpiration(generateExpirationDate()).signWith(SignatureAlgorithm.HS512, secret).compact();
}
private Date generateExpirationDate() {
return new Date(System.currentTimeMillis() + expiration * 1000);
}
}
The code for CsrfHeaderFilter is as following
package com.jwt.security;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.WebUtils;
public class CsrfHeaderFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// TODO Auto-generated method stub
System.out.println("...CsrfToken.class.getName() :::" + CsrfToken.class.getName());
// CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
// CsrfToken csrfToken = new HttpSessionCsrfTokenRepository().loadToken(request);
CsrfToken csrfToken = (CsrfToken) request.getAttribute("_csrf");
String token = null;
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
if(csrfToken != null) {
token = csrfToken.getToken();
}
if (cookie == null || token != null && !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
filterChain.doFilter(request, response);
}
}
The controller used is AuthenticationController The code is as following
package com.jwt.security.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.jwt.security.JwtTokenUtil;
import com.jwt.security.JwtUser;
import com.jwt.security.domain.User;
import com.jwt.security.domain.UserDTO;
import com.jwt.security.exception.UnauthorizedException;
#RestController
public class AuthenticationController {
#Value("${jwt.header}")
private String tokenHeader;
#Autowired private AuthenticationManager authenticationManager;
#Autowired private JwtTokenUtil jwtTokenUtil;
#PostMapping(value="/login")
public ResponseEntity<UserDTO> login(#RequestBody User user, HttpServletRequest request , HttpServletResponse response) {
try {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
System.out.println("matches ::" + encoder.matches("123", user.getPassword()));
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getEmail(), user.getPassword()));
final JwtUser userDetails = (JwtUser)authentication.getPrincipal();
SecurityContextHolder.getContext().setAuthentication(authentication);
final String token = jwtTokenUtil.generateToken(userDetails);
response.setHeader("Token", token);
return new ResponseEntity<UserDTO>(new UserDTO(userDetails.getUser(), token) , HttpStatus.OK);
}catch(UnauthorizedException ex) {
ex.printStackTrace();
throw new UnauthorizedException(ex.getMessage());
}
}
}
On calling http://localhost:8080/login from postman and passing the correct email and password , we are getting the following exception
org.springframework.security.authentication.LockedException: User account is locked
Please advice
The message says "User account is locked". This happens after a number of failed authentication events. The account eventually becomes unlocked depending on implementation.
There are only two places that happens in Spring Security:
AccountStatusUserDetailsChecker.check(UserDetails user)
public void check(UserDetails user) {
if (!user.isAccountNonLocked()) {
throw new LockedException(messages.getMessage(
"AccountStatusUserDetailsChecker.locked", "User account is locked"));
}
if (!user.isEnabled()) {
throw new DisabledException(messages.getMessage(
"AccountStatusUserDetailsChecker.disabled", "User is disabled"));
}
if (!user.isAccountNonExpired()) {
throw new AccountExpiredException(
messages.getMessage("AccountStatusUserDetailsChecker.expired",
"User account has expired"));
}
if (!user.isCredentialsNonExpired()) {
throw new CredentialsExpiredException(messages.getMessage(
"AccountStatusUserDetailsChecker.credentialsExpired",
"User credentials have expired"));
}
}
AbstractUserDetailsAuthenticationProvider.DefaultPreAuthenticationChecks.check(UserDetails user)
So if you want to set a breakpoint, that's where you start.
All of this happens in your UserDetailsService which you have in your configuration.
#Autowired private UserDetailsService userDetailsService;
This service returns an object that implements the UserDetails interface
public interface UserDetails {
boolean isAccountNonLocked();
}
if this method returns false, the account is locked. the name is a bit confusing.
Since we don't know what your UserDetailsService is, we can't tell you how this gets populated. So the recommendation is to just set a break point when the error is thrown.
If you don't want the account locking feature to be enabled, there are different ways to implement that. If you override the UserDetailsService bean you can always return users that are never locked.
Another way is to inject your own checker
DaoAuthenticationProvider daoProvider = ....
daoProvider.setPreAuthenticationChecks(toCheck -> {});
There is also a PostAuthenticationChecks object to see if your password has expired.

How to authorize the menu from web page using the spring boot security sec:authorize-url or using EL expressions?

I'm working on the spring boot security using basic authentication.I have configured basic application security layer and i can authenticate the user.But,now i would like to know how to authorize the user to show only the granted operations without hard coding the role names in client side or server side code using thymeleaf with spring security.
Let's say i have app with some menus in dashboard to show the granted menus to the logged in user.I have read some thymeleaf tutorials by following this link https://github.com/thymeleaf/thymeleaf-extras-springsecurity but i could not get any samples to work using this.
Could anyone help me to know about this scenario ?
import com.triesten.dost.*;
import com.triesten.dost.customUserService.MyCustomUserDetailsService;
import com.triesten.dost.dao.UserDao;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.Order;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {
#Autowired
UserDao userDao;
#Autowired
#Qualifier("userDetailsService")
UserDetailsService userDetailsService;
#Autowired
private RESTAuthenticationEntryPoint authenticationEntryPoint;
#Autowired
private RESTAuthenticationFailureHandler authenticationFailureHandler;
#Autowired
private RESTAuthenticationSuccessHandler authenticationSuccessHandler;
/*#Override
public void configure(WebSecurity web) throws Exception {
web
// Spring Security ignores request to static resources such as
// CSS or JS files.
.ignoring().antMatchers("/static/**");
}*/
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/fonts/**", "/images/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/", "/index.html", "/login", "/dost/bannerContent").permitAll();
http.authorizeRequests().anyRequest().fullyAuthenticated().and().httpBasic().and().csrf().disable();
http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
http.formLogin().loginPage("/login").loginProcessingUrl("/login/authenticate")
.successHandler(authenticationSuccessHandler);
http.formLogin().failureHandler(authenticationFailureHandler);
http.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/login")
.invalidateHttpSession(true);
http.exceptionHandling().accessDeniedHandler(accessDeniedHandler());
// CSRF tokens handling
http.addFilterAfter(new CsrfTokenResponseHeaderBindingFilter(), CsrfFilter.class);
}
/**
* Configures the authentication manager bean which processes authentication
* requests.
*/
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// Dao based authentication
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
private AccessDeniedHandler accessDeniedHandler() {
return new AccessDeniedHandler() {
#Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.getWriter().append("Access denied");
response.setStatus(403);
}
};
}
/**
* This is used to hash the password of the user.
*/
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(10);
}
/**
* This bean is load the user specific data when form login is used.
*/
#Bean
public UserDetailsService userDetailsService() {
return new MyCustomUserDetailsService(userDao);
}
}
Thanks in advance...

Spring boot using Spring Security authentication failure when using SpringPlainTextPasswordValidationCallbackHandler in an XwsSecurityInterceptor

I have set a up a spring boot (1.2.3) application with spring security and spring-ws. I have configured spring security to use .ldapAuthentication() for authentication in my WebSecurityConfigurerAdapter. I am trying to get the same spring security authenticationManager to authenticate my spring ws SOAP web services using ws-security usernametokens (plain text) in my WsConfigurerAdapter.
I have configured my WebSecurityConfigurerAdapter like this:
package za.co.switchx.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
#ConfigurationProperties(prefix="ldap.contextSource")
public LdapContextSource contextSource() {
LdapContextSource contextSource = new LdapContextSource();
return contextSource;
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.userSearchBase("cn=Users,dc=SwitchX,dc=co,dc=za")
.userSearchFilter("(uid={0})")
.groupSearchBase("cn=Groups,dc=SwitchX,dc=co,dc=za")
.groupSearchFilter("(&(cn=*)(| (objectclass=groupofUniqueNames)(objectclass=orcldynamicgroup)))")
.contextSource(contextSource());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/ws/**").permitAll()
.anyRequest().authenticated()
.and()
.csrf().disable()
.httpBasic();
}
}
So then I went to configure my WsConfigurerAdapter like this:
package za.co.switchx.config;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.ws.config.annotation.EnableWs;
import org.springframework.ws.config.annotation.WsConfigurerAdapter;
import org.springframework.ws.transport.http.MessageDispatcherServlet;
import org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition;
import org.springframework.xml.xsd.SimpleXsdSchema;
import org.springframework.xml.xsd.XsdSchema;
import org.springframework.ws.soap.security.xwss.XwsSecurityInterceptor;
import org.springframework.ws.soap.security.xwss.callback.SpringPlainTextPasswordValidationCallbackHandler;
import org.springframework.ws.server.EndpointInterceptor;
#EnableWs
#Configuration
public class WebServiceConfig extends WsConfigurerAdapter {
#Bean
public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean(servlet, "/ws/*");
}
#Bean(name = "ApplicantTypeService")
public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema applicantTypeServiceSchema) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("ApplicantTypePort");
wsdl11Definition.setLocationUri("/ws/ApplicantTypeService");
wsdl11Definition.setTargetNamespace("http://switchx.co.za/services/applicant/types/applicant-type-web-service");
wsdl11Definition.setSchema(applicantTypeServiceSchema);
return wsdl11Definition;
}
#Bean
public XsdSchema applicantTypeSchema() {
return new SimpleXsdSchema(new ClassPathResource("xsd/ApplicantTypeService.xsd"));
}
#Bean
public XwsSecurityInterceptor securityInterceptor() {
XwsSecurityInterceptor securityInterceptor = new XwsSecurityInterceptor();
securityInterceptor.setCallbackHandler(new SpringPlainTextPasswordValidationCallbackHandler());
securityInterceptor.setPolicyConfiguration(new ClassPathResource("securityPolicy.xml"));
return securityInterceptor;
}
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
interceptors.add(securityInterceptor());
}
}
If I use a SimplePasswordValidationCallbackHandler in the XwsSecurityInterceptor it does authenticate the ws usernametoken correctly, so I know there is nothing wrong with the ws-security section. And if I logon via http basic it authenticates my ldap user correctly so I know that works.
The problem is that when I try use my ldap user logon in the ws security usernametoken I get ERROR c.s.xml.wss.logging.impl.filter - WSS1408: UsernameToken Authentication Failed in the logs, so looks like its not using my global ldap authentication defined in the WebSecurityConfigAdapter
I cant seem to figure out how to get the SpringPlainTextPasswordValidationCallbackHandler (which is supposed to use spring security) in the XwsSecurityInterceptor to use the global authenticationManager, please help?? I have really been bashing my head against this for the last day but cant seem to win.
Ok I figured this out so though I would post for anyone trying this in the future.
I resolved this problem by changing my spring boot class to:
#SpringBootApplication
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SwitchxApplication extends WebMvcConfigurerAdapter {
#SuppressWarnings("unused")
private static final Logger log = LoggerFactory.getLogger(SwitchxApplication.class);
#Bean
public ApplicationSecurity applicationSecurity() {
return new ApplicationSecurity();
}
#Configuration
#Order(Ordered.HIGHEST_PRECEDENCE)
protected static class AuthenticationConfiguration extends GlobalAuthenticationConfigurerAdapter {
#Bean
#ConfigurationProperties(prefix="ldap.contextSource")
public LdapContextSource contextSource() {
LdapContextSource contextSource = new LdapContextSource();
return contextSource;
}
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.userSearchBase("cn=Users,dc=Blah,dc=co,dc=za")
.userSearchFilter("(uid={0})")
.groupSearchBase("cn=Groups,dc=Blah,dc=co,dc=za")
.groupSearchFilter("(&(cn=*)(|(objectclass=groupofUniqueNames)(objectclass=orcldynamicgroup)))")
.contextSource(contextSource());
}
}
#Order(Ordered.LOWEST_PRECEDENCE - 8)
protected static class ApplicationSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/ws/**").permitAll()
.anyRequest().authenticated()
.and()
.csrf().disable()
.httpBasic();
}
}
public static void main(String[] args) {
SpringApplication.run(SwitchxApplication.class, args);
}
}
And then made the following relevant changes in my WsConfigurerAdapter to:
#EnableWs
#Configuration
public class WebServiceConfig extends WsConfigurerAdapter {
private static final Logger log = LoggerFactory.getLogger(WebServiceConfig.class);
#Autowired
private AuthenticationManager authenticationManager;
#Bean
public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean(servlet, "/ws/*");
}
.....
.....
#Bean
public SpringPlainTextPasswordValidationCallbackHandler callbackHandler() {
SpringPlainTextPasswordValidationCallbackHandler callbackHandler = new SpringPlainTextPasswordValidationCallbackHandler();
try {
callbackHandler.setAuthenticationManager(authenticationManager);
} catch(Exception e) {
log.error(e.getMessage());
}
return callbackHandler;
}
#Bean
public XwsSecurityInterceptor securityInterceptor() {
XwsSecurityInterceptor securityInterceptor = new XwsSecurityInterceptor();
securityInterceptor.setCallbackHandler(callbackHandler());
securityInterceptor.setPolicyConfiguration(new ClassPathResource("securityPolicy.xml"));
return securityInterceptor;
}
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
interceptors.add(securityInterceptor());
}
}
So basically the end result is that for all /ws paths the basic http security is ignored but because of the security intercepter in the WS Config it will use a basic ws-security user name token to authenticate web service calls, allowing you to have both authentication mechanisms using spring security set up with ldap.
I hope this helps someone, was a bit tricky not finding of lot of documentation on the boot and java config documentation on this particular setup and stuff as it still relatively new. But after not getting this working, its pretty awesome and Im very impressed.

How to define the response url at OpenIDAuthenticationFilter?

I have a CustomOpenIDAuthenticationFilter extends org.springframework.security.openid.OpenIDAuthenticationFilter. I want to define the response url after the authentication is successful, but do not know how to do it. Any help you might have would be very much appreciated.
I have the following code at the moment:
public class CustomOpenIDAuthenticationFilter extends OpenIDAuthenticationFilter{
protected static Logger logger = Logger.getLogger("service");
public CustomOpenIDAuthenticationFilter(){
super();
ProxyProperties proxyProps = new ProxyProperties();
proxyProps.setProxyHostName(PROXYNAME);
proxyProps.setProxyPort(PROXYPORT);
HttpClientFactory.setProxyProperties(proxyProps);
}
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException {
//i think the response url should be defined here.
Authentication au = super.attemptAuthentication(request, response);
return au;
}
}
Edit
Sorry for your time, i did not explain my problem correctly.
So, when my login page is sending authentication request to openid provider, the request contains a returnToUrl, where is "The URL on the Consumer site where the OpenID Provider will return the user after generating the authentication response. ". On a non-spring application, i would do
AuthRequest authRequest = manager.authenticate(discovered, returnToUrl);
My question is how could I specify this returnToUrl at my CustomOpenIDAuthenticationFilter.
To specify the returnToUrl you can override the String buildReturnToUrl(HttpServletRequest request) method. An example of making this an arbitrary URL is given below:
public class CustomOpenIDAuthenticationFilter extends OpenIDAuthenticationFilter {
...
protected String buildReturnToUrl(HttpServletRequest request) {
// this URL needs to be processed by CustomOpenIDAuthenticationFilter to validate
// the OpenID response and authenticate the user
return "https://example.com";
}
}
As the comment mentions this URL should be a URL that CustomOpenIDAuthenticationFilter will process since it is what validates the OpenID response.
This can also be achieved by creating a custom filter an place it before OPENID_FILTER
</http>
...
<custom-filter before="OPENID_FILTER" ref="myBeforeOpenIDFilter" />
</http>
<beans:bean id="myBeforeOpenIDFilter"class="com.example.provider.openid.MyBeforeOpenIdFilter" />
And below there is my implementation of this custom filter
package com.example.provider.openid;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyBeforeOpenIdFilter implements Filter{
static Logger logger = LoggerFactory.getLogger(MyBeforeOpenIdFilter.class);
static class FilteredRequest extends HttpServletRequestWrapper {
public FilteredRequest(HttpServletRequest request) {
super(request);
}
#Override
public java.lang.StringBuffer getRequestURL(){
String baseUrl = (String) super.getSession().getServletContext().getAttribute("applicationBaseUrl");
StringBuffer sb = super.getRequestURL();
int index = sb.indexOf("/j_spring_openid_security_check");
if(index != -1){
// here replace the host etc with proper value
if(baseUrl.endsWith("/")){
baseUrl = baseUrl.substring(0, baseUrl.length()-1);
}
logger.debug("Changing the getRequestURL to inject the correct host so openid login could work behind proxy");
logger.debug("Original getRequestURL: "+sb.toString());
logger.debug("Replacing the baseUrl with: "+baseUrl);
sb.replace(0, index, baseUrl);
logger.debug("New getRequestURL: "+sb.toString());
}
return sb;
}
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
//No need to init
}
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
chain.doFilter(new FilteredRequest((HttpServletRequest) request), response);
}
#Override
public void destroy() {
//No need to destroy
}
}
In this way you can define your openid provider using the default namespace
and have the filter plugin out if you need it. In my implementation I'm taking the baseUrl from the servlet context but it can be simply hardcoded
Hope this will help someone
Cheers
Szymon

Resources