Selection based AuthProvider Spring Security - java

I have configured Spring security to work with both LDAP and DB based login. First it tries to login via LDAP and if required permissions is not there then it takes to username/password entry page.
<security:http auto-config="false" entry-point-ref="loginUrlAuthenticationEntryPoint">
<security:custom-filter ref="customPreAuthFilter" position="PRE_AUTH_FILTER"/> // This is for LDAP
<security:custom-filter ref="customAuthFilter" position="FORM_LOGIN_FILTER"/> // This is for DB Based
/** intercept urls
**/
</security:http>
I want to add a new screen on the top and user need to select between the two button LDAP or username/pass. How do I proceed?
The data to be accessed is the same url i.e. /home but both ldap and DB users should be able to access.

If you look at code in UsernamePasswordAuthenticationFilter there is setDetails method.
from docs:
Provided so that subclasses may configure what is put into the
authentication request's details property.
Idea from here
Provision to change ldap/Ad provider url at run time
You can set the details like authtype here and use it authentication provider, But to achieve the things you would lik adds little more work.
Adding details and hope it helps.
CustomUsernamePasswordAuthenticationFilter.java
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.stereotype.Component;
#Component
public class CustomUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private static final Logger logger = LoggerFactory.getLogger(CustomUsernamePasswordAuthenticationFilter.class);
#Autowired
#Override
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
// TODO Auto-generated method stub
super.setAuthenticationManager(authenticationManager);
}
#Autowired
#Override
public void setAuthenticationDetailsSource(
AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
super.setAuthenticationDetailsSource(authenticationDetailsSource);
}
#Override
protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
String authType = request.getParameter("authType");
logger.info("authType {} ",authType);
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
}
But this is not sufficient you would need to extend WebAuthenticationDetails.
Reason is WebAuthenticationDetails provides only remote IP address and sessionId so, to add authType we need to extend this class.
You have to extend WebAuthenticationDetailsSource to return CustomAuthenticationDetails as shown below.
CustomAuthenticationDetails.java
public class CustomAuthenticationDetails extends WebAuthenticationDetails{
private final String authType;
public CustomAuthenticationDetails(HttpServletRequest request) {
super(request);
authType = request.getParameter("authType");
}
public String getAuthType() {
return authType;
}
}
CustomWebAuthenticationDetailsSource.java
public class CustomWebAuthenticationDetailsSource extends WebAuthenticationDetailsSource {
#Override
public WebAuthenticationDetails buildDetails(HttpServletRequest context) {
return new CustomAuthenticationDetails(context);
}
}
Please note these classes for demo purpose only.
Need to autowire actual authentication providers in these classes.
import java.util.Arrays;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.ldap.authentication.LdapAuthenticationProvider;
import org.springframework.stereotype.Component;
#Component
public class AuthenicationProviderJdbcLdapImpl implements AuthenticationProvider{
// you need to autowire jdbc auth provider
#Autowired(required = false)
DaoAuthenticationProvider authenticationProvider;
//you need to autowire ldap auth provider
#Autowired(required = false)
LdapAuthenticationProvider ldapAuthenticationProvider;
protected static class User{
private final String userId;
private final String password;
public User(String userId,String password) {
this.userId = userId;
this.password = password;
}
public String getUserId() {
return userId;
}
public String getPassword() {
return password;
}
#Override
public String toString() {
return "User [userId=" + userId + ", password=" + password + "]";
}
}
private final static Logger logger = LoggerFactory.getLogger(AuthenicationProviderJdbcLdapImpl.class);
private static final List<User> users1 = Arrays.asList(new User("admin1", "password"),new User("admin2", "password"));
private static final List<User> users2 = Arrays.asList(new User("admin3", "password"),new User("admin4", "password"));
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
CustomAuthenticationDetails details = (CustomAuthenticationDetails) authentication.getDetails();
String authType = details.getAuthType();
logger.info("authType {}",authType);
if("jdbc".equalsIgnoreCase(authType)) {
logger.info("perfrom jdbc authentication");
//perform your authentication using jdbc
//the below is just for explaination
return performAuthentication(authentication, users1);
}else if("ldap".equalsIgnoreCase(authType)) {
logger.info("perfrom ldap authentication");
//perform your authentication using ldap
//the below is just for explaination
return performAuthentication(authentication, users2);
}
return null;
}
private Authentication performAuthentication(Authentication authentication,List<User> users) {
String credential = (String) authentication.getCredentials();
String userId = authentication.getName();
for(User user: users) {
if(user.getUserId().equals(userId)&& user.getPassword().equals(credential)) {
authentication = new UsernamePasswordAuthenticationToken(authentication.getPrincipal(), authentication.getCredentials(),authentication.getAuthorities());
return authentication;
}
}
return null;
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
If you would need to redirect different login page (not sure, if you have the requirement) you register AuthenticationFailureHandler shown in security config. Here it is redirected to login and login1 based on condition.
http.failureHandler(new AuthenticationFailureHandler() {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
String whichPage = request.getParameter("whichPage");
System.out.println("inside login failure handler "+whichPage);
if("login1".equals(whichPage)) {
response.sendRedirect(request.getContextPath() +"/login1");
}else {
response.sendRedirect(request.getContextPath() +"/login");
}
}
})
WebSecurityConfig.java
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public AuthenticationManager getAuthenticationManager() throws Exception {
return super.authenticationManagerBean();
}
#Autowired
AuthenticationSuccessHandler authenticationSuccessHandler;
#Autowired()
AuthenicationProviderJdbcImpl authenicationProviderJdbcImpl;
#Autowired()
AuthenicationProviderLdapImpl authenicationProviderLdapImpl;
#Autowired
CustomUsernamePasswordAuthenticationFilter customUsernamePasswordAuthenticationFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterAt(customUsernamePasswordAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
http
.authorizeRequests()
.antMatchers("/resources/**", "/registration","/login1").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()//.successHandler(authenticationSuccessHandler)
.failureHandler(new AuthenticationFailureHandler() {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
String whichPage = request.getParameter("whichPage");
System.out.println("inside login failure handler "+whichPage);
if("login1".equals(whichPage)) {
response.sendRedirect(request.getContextPath() +"/login1");
}else {
response.sendRedirect(request.getContextPath() +"/login");
}
}
})
.and()
.logout()
.permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenicationProviderLdapImpl).authenticationProvider(authenicationProviderJdbcImpl);
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
/*auth.userDetailsService(userDetailsService)
.passwordEncoder(bCryptPasswordEncoder());*/
}
}
The below is from logs when authType = jdbc or authType=ldap
login called
2018-11-23 17:45:25.606 INFO 7232 --- [nio-8080-exec-6] stomUsernamePasswordAuthenticationFilter : authType jdbc
2018-11-23 17:45:25.606 INFO 7232 --- [nio-8080-exec-6] c.t.a.AuthenicationProviderJdbcLdapImpl : authType jdbc
2018-11-23 17:45:25.606 INFO 7232 --- [nio-8080-exec-6] c.t.a.AuthenicationProviderJdbcLdapImpl : perfrom jdbc authentication
login called
login1 called
login1 called
2018-11-23 17:45:42.435 INFO 7232 --- [nio-8080-exec-5] stomUsernamePasswordAuthenticationFilter : authType ldap
2018-11-23 17:45:42.435 INFO 7232 --- [nio-8080-exec-5] c.t.a.AuthenicationProviderJdbcLdapImpl : authType ldap
2018-11-23 17:45:42.435 INFO 7232 --- [nio-8080-exec-5] c.t.a.AuthenicationProviderJdbcLdapImpl : perfrom ldap authentication returning true in ldap

Related

Spring authentication with neo4j

I am workin on spring application with neo4j. I'd like to add authentication using username and password from the database. Below I put the code on how I'd do it using mysql. I'm wondering whats the equivalent for the code below using neo4j.
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Bean
public PasswordEncoder passwordEncoders(){
return new BCryptPasswordEncoder();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication()
.usersByUsernameQuery("SELECT u.name, u.password, 1 FROM user u WHERE u.name=?")
.authoritiesByUsernameQuery("SELECT u.name, u.role, 1 FROM user u WHERE u.name=?")
.dataSource(dataSource)
.passwordEncoder(passwordEncoders());
}
//...
}
I tried creating Beans for DataSource but I get BeanCreationException. Here is what I tried to use
#Bean
public DataSource getDataSource(){
String NEO4J_URL = System.getenv("NEO4J_URL");
if (NEO4J_URL==null) NEO4J_URL=System.getProperty("NEO4J_URL","jdbc:neo4j:http://localhost:11010");
return new DriverManagerDataSource(NEO4J_URL);
}
or this
#Bean
public DataSource getDataSource(){
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.driverClassName("org.neo4j.driver");
dataSourceBuilder.url("bolt://localhost:11010");
dataSourceBuilder.username("neo4j");
dataSourceBuilder.password("0000");
return dataSourceBuilder.build();
}
You will need to register a custom Security AuthenticationProvider that can query the graph to retrieve the users with the given credentials.
Such a provider would look like this :
package com.ikwattro.demo.neo4jauth.security;
import org.neo4j.driver.Driver;
import org.neo4j.driver.Record;
import org.neo4j.driver.Session;
import org.neo4j.driver.types.Node;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
#Component
public class Neo4jAuthenticationProvider implements AuthenticationProvider {
private final Driver driver;
public Neo4jAuthenticationProvider(Driver driver) {
this.driver = driver;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String name = authentication.getName();
String password = authentication.getCredentials().toString();
try (Session session = driver.session()) {
List<Record> results = session.run("MATCH (n:User) WHERE n.username = $name AND n.password = $password RETURN n",
Map.of("name", name, "password", password)).list();
if (results.isEmpty()) {
return null;
}
Node user = results.get(0).get("n").asNode();
// Possible to add more information from user
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
final UserDetails principal = new User(name, password, authorities);
return new UsernamePasswordAuthenticationToken(principal, password, authorities);
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
You then need to register this provider as your main AuthenticationProvider in the Security chain, you do this in the Security Configuration :
package com.ikwattro.demo.neo4jauth.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
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 SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private Neo4jAuthenticationProvider authenticationProvider;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated()
.and().httpBasic();
}
}
You can then query your application with basic authentication
curl --user john:doe localhost:8080/hello
You can find a fully working demo here : https://github.com/ikwattro/spring-security-neo4j

Spring Security MultiHttpSecurity Configuration so that I can perform two types of authentication. JWT Token and Session Cookie

I already have Spring Security Cookie mechanism in place for my application, now only for the API's, I need to add JWT Token-based authentication mechanism. I'm using Spring Security's MultiHttpSecurityConfiguration with two nested class.
Whether both session and JWT token mechanism should be included together in one application or not is a different question altogether, I need to achieve two things.
Spring Security's session-based authentication with cookie will work as it was before.
Need to add an authentication header for API's
package com.leadwinner.sms.config;
import java.util.Collections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
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.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import com.leadwinner.sms.CustomAuthenticationSuccessHandler;
import com.leadwinner.sms.CustomLogoutSuccessHandler;
import com.leadwinner.sms.config.jwt.JwtAuthenticationProvider;
import com.leadwinner.sms.config.jwt.JwtAuthenticationTokenFilter;
import com.leadwinner.sms.config.jwt.JwtSuccessHandler;
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
#ComponentScan(basePackages = "com.leadwinner.sms")
public class MultiHttpSecurityConfig {
#Autowired
#Qualifier("userServiceImpl")
private UserDetailsService userServiceImpl;
#Autowired
private JwtAuthenticationProvider authenticationProvider;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userServiceImpl).passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(Collections.singletonList(authenticationProvider));
}
#Configuration
#Order(1)
public static class JwtSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationTokenFilter jwtauthFilter;
#Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.antMatcher("/web/umgmt/**").authorizeRequests()
.antMatchers("/web/umgmt/**").authenticated()
.and()
.addFilterBefore(jwtauthFilter, UsernamePasswordAuthenticationFilter.class);
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
#Configuration
#Order(2)
public static class SecurityConfig extends WebSecurityConfigurerAdapter {
private final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
#Bean
public CustomAuthenticationEntryPoint getBasicAuthEntryPoint() {
return new CustomAuthenticationEntryPoint();
}
#Override
public void configure(HttpSecurity http) throws Exception {
logger.info("http configure");
http
.antMatcher("/**").authorizeRequests()
.antMatchers("/login/authenticate").permitAll()
.antMatchers("/resources/js/**").permitAll()
.antMatchers("/resources/css/**").permitAll()
.antMatchers("/resources/images/**").permitAll()
.antMatchers("/web/initial/setup/**").permitAll()
.antMatchers("/dsinput/**").permitAll().antMatchers("/dsoutput/**").permitAll()
.and()
.formLogin()
.loginPage("/login").usernameParameter("employeeId").passwordParameter("password")
.successForwardUrl("/dashboard")
.defaultSuccessUrl("/dashboard", true)
.successHandler(customAuthenticationSuccessHandler())
.failureForwardUrl("/logout")
.loginProcessingUrl("/j_spring_security_check")
.and().logout()
.logoutSuccessUrl("/logout").logoutUrl("/j_spring_security_logout")
.logoutSuccessHandler(customLogoutSuccessHandler())
.permitAll()
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.and().sessionManagement()
.sessionFixation().none()
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
.invalidSessionUrl("/logout")
.and().exceptionHandling().accessDeniedPage("/logout").and().csrf().disable();
http.authorizeRequests().anyRequest().authenticated();
}
#Bean
public AuthenticationSuccessHandler customAuthenticationSuccessHandler() {
return new CustomAuthenticationSuccessHandler();
}
#Bean
public LogoutSuccessHandler customLogoutSuccessHandler() {
return new CustomLogoutSuccessHandler();
}
}
}
JwtAuthenticationTokenFilter.java
package com.leadwinner.sms.config.jwt;
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.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String header = request.getHeader("Authorization");
if (header != null && header.startsWith("Bearer ")) {
String authToken = header.substring(7);
System.out.println(authToken);
try {
String username = jwtTokenUtil.getUsernameFromToken(authToken);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
if (jwtTokenUtil.validateToken(authToken, username)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
username, null, null);
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
} catch (Exception e) {
System.out.println("Unable to get JWT Token, possibly expired");
}
}
chain.doFilter(request, response);
}
}
JwtTokenUtil.java
package com.leadwinner.sms.config.jwt;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import org.springframework.stereotype.Component;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
#Component
public class JwtTokenUtil implements Serializable {
private static final long serialVersionUID = 8544329907338151549L;
public static final long JWT_TOKEN_VALIDITY = 5 * 60 * 60;
private String secret = "my-secret";
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
public String generateToken(String username) {
Map<String, Object> claims = new HashMap<>();
return doGenerateToken(claims, username);
}
private String doGenerateToken(Map<String, Object> claims, String subject) {
return "Bearer "
+ Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000))
.signWith(SignatureAlgorithm.HS512, secret).compact();
}
public Boolean validateToken(String token, String usernameFromToken) {
final String username = getUsernameFromToken(token);
return (username.equals(usernameFromToken) && !isTokenExpired(token));
}
}
It seems that now the JwtSecurityConfig filter is not being applied for the path I have mentioned.
Any help will be appreciated.
I have already read this question. I followed the same.
Spring Security with Spring Boot: Mix Basic Authentication with JWT token authentication
Edit: Added JwtAuthenticationTokenFilter, JwtTokenUtil
I got your requirement.
You need to expose API's that should be accessed through JWT token in request header(for each request).
And also web application should be secured through form based authentication mechanism which should work on basis of http session.
You can achieve this by two authentication filters.
Filter - 1: for Rest API (JwtAuthTokenFilter) which should be stateless and identified by Authorization token sent in request each time.
Filter - 2: You need another filter (UsernamePasswordAuthenticationFilter) By default spring-security provides this if you configure it by http.formLogin(). Here each request is identified by the session(JSESSIONID cookie) associated. If request does not contain valid session then it will be redirected to authentication-entry-point (say: login-page).
Recommended URL pattern
api-url-pattern = "/api/**" [strictly for #order(1)]
webApp-url-pattern = "/**" [ wild card "/**" always used for higer order otherwise next order configuration becomes dead configuration]
Approach
Define Main Configuration class with #EnableWebSecurity
Create two inner static classes which should extend WebSecurityConfigurerAdapter and annotated with #Configuration and #Order. Here order for rest api configuration should be 1 and for web application configuration order should be more than 1
Refer my answer in this link for more details which has explaination in depth with necessary code. Feel free to ask for downloadable link from github repository if required.
Limitation
Here both filters will work side by side(Parellally). I mean from web application even though if a user is authenticated by session, he can not access API's without a JWT token.
EDIT
For OP's requirement where he doesn't want to define any role but API access is allowed for authenticated user. For his requirement modified below configuration.
http.csrf().disable()
.antMatcher("/web/umgmt/**").authorizeRequests()
.antMatcher("/web/umgmt/**").authenticated() // use this

Spring Security: Custom UserDetailsService not being called (using Auth0 authentication)

I'm new to the Spring framework, so I apologize in advance for any gaping holes in my understanding.
I'm using Auth0 to secure my API, which works perfectly. My setup & config is the same as the suggested setup in the Auth0 documentation:
// SecurityConfig.java
#Configuration
#EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// auth0 config vars here
#Override
protected void configure(HttpSecurity http) {
JwtWebSecurityConfigurer
.forRS256(apiAudience, issuer)
.configure(http)
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/api/public").permitAll()
.antMatchers(HttpMethod.GET, "/api/private").authenticated();
}
}
With this setup, the spring security principal is being set to the userId (sub) from the jwt token: auth0|5b2b.... However, instead of just the userId, I want it set to the matching user (from my database). My question is how to do that.
What I've tried
I've tried implementing a custom database-backed UserDetailsService that I copied from this tutorial.
However, it's not getting called regardless of how I try to add it to my conf. I've tried adding it several different ways with no effect:
// SecurityConfig.java (changes only)
// My custom userDetailsService, overriding the loadUserByUsername
// method from Spring Framework's UserDetailsService.
#Autowired
private MyUserDetailsService userDetailsService;
protected void configure(HttpSecurity http) {
http.userDetailsService(userDetailsService); // Option 1
http.authenticationProvider(authenticationProvider()); // Option 2
JwtWebSecurityConfigurer
[...] // The rest unchanged from above
}
#Override // Option 3 & 4: Override the following method
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(authenticationProvider()); // Option 3
auth.userDetailsService(userDetailsService); // Option 4
}
#Bean // Needed for Options 2 or 4
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
return authProvider;
}
Unfortunately none of the similar "userDetails not being called" questions have helped me due to me needing to combine it with Auth0 authentication.
I'm not positive that I'm on the right path with this. It seems strange to me that I can't find any documentation from Auth0 on this extremely common use case, so maybe I'm missing something obvious.
PS: Not sure if relevant, but the following is always logged during init.
Jun 27, 2018 11:25:22 AM com.test.UserRepository initDao
INFO: No authentication manager set. Reauthentication of users when changing passwords will not be performed.
EDIT 1:
Based on Ashish451's answer, I tried copying his CustomUserDetailsService, and added the following to my SecurityConfig:
#Autowired
private CustomUserDetailsService userService;
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Autowired
public void configureGlobal( AuthenticationManagerBuilder auth ) throws Exception {
auth.userDetailsService( userService );
}
Unfortunately with those changes, CustomUserDetailsService is still not being called.
EDIT 2:
Output when adding the logging method suggested by #Norberto Ritzmann:
Jul 04, 2018 3:49:22 PM com.test.repositories.UserRepositoryImpl initDao
INFO: No authentication manager set. Reauthentication of users when changing passwords will not be performed.
Jul 04, 2018 3:49:22 PM com.test.runner.JettyRunner testUserDetailsImpl
INFO: UserDetailsService implementation: com.test.services.CustomUserDetailsService
Looking at your Adapter code you are generating JWT token in configure itself.
Am not sure whats apiAudience, issuer but it generated sub of JWT I guess.
Your issue is that you want to change JWT sub as per your Database.
I have recently implemented JWT security in Spring Boot Application.
And I am setting UserName after fetching it from Database.
I have added code with pkg info for clarity.
// My adapter class. Its same as your's except one thing that I have added a Filter to it. In this Filter I am authenticating JWT token. This filter will be called each time a Secured Rest URL is fired.
import java.nio.charset.StandardCharsets;
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.builders.WebSecurity;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
import com.dev.myapp.jwt.model.CustomUserDetailsService;
import com.dev.myapp.security.RestAuthenticationEntryPoint;
import com.dev.myapp.security.TokenAuthenticationFilter;
import com.dev.myapp.security.TokenHelper;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Autowired
private CustomUserDetailsService jwtUserDetailsService; // Get UserDetail bu UserName
#Autowired
private RestAuthenticationEntryPoint restAuthenticationEntryPoint; // Handle any exception during Authentication
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
// Binds User service for User and Password Query from Database with Password Encryption
#Autowired
public void configureGlobal( AuthenticationManagerBuilder auth ) throws Exception {
auth.userDetailsService( jwtUserDetailsService )
.passwordEncoder( passwordEncoder() );
}
#Autowired
TokenHelper tokenHelper; // Contains method for JWT key Generation, Validation and many more...
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement().sessionCreationPolicy( SessionCreationPolicy.STATELESS ).and()
.exceptionHandling().authenticationEntryPoint( restAuthenticationEntryPoint ).and()
.authorizeRequests()
.antMatchers("/auth/**").permitAll()
.anyRequest().authenticated().and()
.addFilterBefore(new TokenAuthenticationFilter(tokenHelper, jwtUserDetailsService), BasicAuthenticationFilter.class);
http.csrf().disable();
}
// Patterns to ignore from JWT security check
#Override
public void configure(WebSecurity web) throws Exception {
// TokenAuthenticationFilter will ignore below paths
web.ignoring().antMatchers(
HttpMethod.POST,
"/auth/login"
);
web.ignoring().antMatchers(
HttpMethod.GET,
"/",
"/assets/**",
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js"
);
}
}
// User Service to get User Details
#Transactional
#Repository
public class CustomUserDetailsService implements UserDetailsService {
protected final Log LOGGER = LogFactory.getLog(getClass());
#Autowired
private UserRepo userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User uu = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException(String.format("No user found with username '%s'.", username));
} else {
return user;
}
}
}
// Unauthorized access handler
#Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
// This is invoked when user tries to access a secured REST resource without supplying any credentials
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
}
}
// Filter Chain for Validating JWT Token
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.filter.OncePerRequestFilter;
public class TokenAuthenticationFilter extends OncePerRequestFilter {
protected final Log logger = LogFactory.getLog(getClass());
private TokenHelper tokenHelper;
private UserDetailsService userDetailsService;
public TokenAuthenticationFilter(TokenHelper tokenHelper, UserDetailsService userDetailsService) {
this.tokenHelper = tokenHelper;
this.userDetailsService = userDetailsService;
}
#Override
public void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain chain
) throws IOException, ServletException {
String username;
String authToken = tokenHelper.getToken(request);
logger.info("AuthToken: "+authToken);
if (authToken != null) {
// get username from token
username = tokenHelper.getUsernameFromToken(authToken);
logger.info("UserName: "+username);
if (username != null) {
// get user
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (tokenHelper.validateToken(authToken, userDetails)) {
// create authentication
TokenBasedAuthentication authentication = new TokenBasedAuthentication(userDetails);
authentication.setToken(authToken);
SecurityContextHolder.getContext().setAuthentication(authentication); // Adding Token in Security COntext
}
}else{
logger.error("Something is wrong with Token.");
}
}
chain.doFilter(request, response);
}
}
// TokenBasedAuthentication class
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
public class TokenBasedAuthentication extends AbstractAuthenticationToken {
private static final long serialVersionUID = -8448265604081678951L;
private String token;
private final UserDetails principle;
public TokenBasedAuthentication( UserDetails principle ) {
super( principle.getAuthorities() );
this.principle = principle;
}
public String getToken() {
return token;
}
public void setToken( String token ) {
this.token = token;
}
#Override
public boolean isAuthenticated() {
return true;
}
#Override
public Object getCredentials() {
return token;
}
#Override
public UserDetails getPrincipal() {
return principle;
}
}
// Helper class for JWT generation and Validation Logic
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import com.dev.myapp.common.TimeProvider;
import com.dev.myapp.entity.User;
#Component
public class TokenHelper {
protected final Log LOGGER = LogFactory.getLog(getClass());
#Value("${app.name}") // reading details from property file added in Class path
private String APP_NAME;
#Value("${jwt.secret}")
public String SECRET;
#Value("${jwt.licenseSecret}")
public String LICENSE_SECRET;
#Value("${jwt.expires_in}")
private int EXPIRES_IN;
#Value("${jwt.mobile_expires_in}")
private int MOBILE_EXPIRES_IN;
#Value("${jwt.header}")
private String AUTH_HEADER;
#Autowired
TimeProvider timeProvider; // return current time. Basically Deployment time.
private SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS512;
// Generate Token based on UserName. You can Customize this
public String generateToken(String username) {
String audience = generateAudience();
return Jwts.builder()
.setIssuer( APP_NAME )
.setSubject(username)
.setAudience(audience)
.setIssuedAt(timeProvider.now())
.setExpiration(generateExpirationDate())
.signWith( SIGNATURE_ALGORITHM, SECRET )
.compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
User user = (User) userDetails;
final String username = getUsernameFromToken(token);
final Date created = getIssuedAtDateFromToken(token);
return (
username != null &&
username.equals(userDetails.getUsername())
);
}
// If Token is valid will extract all claim else throw appropriate error
private Claims getAllClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
LOGGER.error("Could not get all claims Token from passed token");
claims = null;
}
return claims;
}
private Date generateExpirationDate() {
long expiresIn = EXPIRES_IN;
return new Date(timeProvider.now().getTime() + expiresIn * 1000);
}
}
For this Log
No authentication manager set. Reauthentication of users when changing passwords
Since you have not implemented a methods with Name loadUserByUsername. You are getting this log.
Edit 1:
I am using Filter Chain just to Validate Token and add User in Security Context which will be extracted from Token....
Am using JWT and you are using AuthO, only Implementation is Different. Am added full implementation for a complete work flow.
You focus on implementing authenticationManagerBean and configureGlobal from WebSecurityConfig class to use UserService.
and TokenBasedAuthentication class implementation.
Other things you can skip.
Maybe this is an spring-boot context initialization issue, meaning the #Autowired annotation cannot be resolved during the initialization of the Configuration class.
You could try the #ComponentScan() annotation on top of your Configuration class and load your MyUserDetailsService explicitly. (see: https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-configuration-classes.html#using-boot-importing-configuration). Having done this I would recommend the following in your Configuration class:
#Autowired
private MyUserDetailsService userService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}
Hope this can help you out.
I ended up asking Auth0 support about this, and they say that it's currently not possible to modify the principal without modifying the library source.
They provide an alternative approach, however, which is to use a JWT validation library (e.g. https://github.com/auth0/java-jwt) instead of their Spring Security API SDK.
My solution will be to modify my code to work with just the token as principal.
You could extend JwtAuthenticationProvider with overridden authenticate method which will put your user into Authentication object:
Using springboot 2.1.7.RELEASE
Auth0 deps: com.auth0:auth0:1.14.2, com.auth0:auth0-spring-security-api:1.2.5, com.auth0:jwks-rsa:0.8.3
Note: some errors in the following code snippets might exist as I've transformed kotlin code by hand into java
Configure SecurityConfig as usual but pass modified authentication provider:
#Autowired UserService userService;
...
#Override
protected void configure(HttpSecurity http) {
// same thing used in usual method `JwtWebSecurityConfigurer.forRS256(String audience, String issuer)`
JwkProvider jwkProvider = JwkProviderBuilder(issuer).build()
// provider deduced from existing default one
Auth0UserAuthenticationProvider authenticationProvider = new Auth0UserAuthenticationProvider(userService, jwkProvider, issuer, audience)
JwtWebSecurityConfigurer
.forRS256(apiAudience, issuer, authenticationProvider)
.configure(http)
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/api/public").permitAll()
.antMatchers(HttpMethod.GET, "/api/private").authenticated();
}
Extend default JwtAuthenticationProvider which is usually used in method JwtWebSecurityConfigurer.forRS256(String audience, String issuer)
public class Auth0UserAuthenticationProvider extends JwtAuthenticationProvider {
private final UserService userService;
public (UserService userService, JwkProvider jwkProvider, String issuer, String audience) {
super(jwkProvider, issuer, audience);
this.userService = userService;
}
/**
* Intercept Authentication object before it is set in context
*/
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Authentication jwtAuth = super.authenticate(authentication);
// Use your service and get user details here
User user = userService.getDetailsOrWhatever(jwtAuth.getPrincipal().toString());
// TODO implement following class which merges Auth0 provided details with your user
return new MyAuthentication(jwtAuth, user);
}
}
Implement your own MyAuthentication.class which will override getDetails() and return the actual user instead of decoded token given by Auth0 library.
Afterwards user will be available in
SecurityContextHolder.getContext().getAuthentication().getDetails();
This is what I ended up doing if you want to just stick with using spring libraries. You can change Auth0UserDetailsService to query user details from any source.
public class Auth0JwtAuthenticationProvider implements AuthenticationProvider {
#Autowired
ApplicationContext context;
#Autowired
#Qualifier("auth0UserDetailsService")
UserDetailsService auth0UserDetailsService;
private JwtAuthenticationProvider jwtAuthenticationProvider;
public Auth0JwtAuthenticationProvider() {
}
#PostConstruct
public void postConstruct() {
jwtAuthenticationProvider = new JwtAuthenticationProvider(context.getBean(JwtDecoder.class));
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Authentication token = jwtAuthenticationProvider.authenticate(authentication);
((AbstractAuthenticationToken) token).setDetails(auth0UserDetailsService.loadUserByUsername(authentication.getName()));
return token;
}
#Override
public boolean supports(Class<?> authentication) {
return BearerTokenAuthenticationToken.class.isAssignableFrom(authentication);
}
}
And for UserDetailsService I am getting user details using a service.
public class Auth0UserDetailsService implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
RestTemplate restTemplate = new RestTemplate();
.....
ResponseEntity<Auth0UserDetails> responseEntity = restTemplate.exchange(url, HttpMethod.GET, entity, Auth0UserDetails.class);
return responseEntity.getBody();
}
.....
}
And UserDetails will have all details you need.
public class Auth0UserDetails implements UserDetails {
#JsonProperty("name")
private String userName;
#JsonProperty("created_at")
private LocalDateTime createdAt;
#JsonProperty("email")
private String email;
#JsonProperty("email_verified")
private boolean isEmailVerified;
#JsonProperty("nickname")
private String nickName;
#JsonProperty("picture")
private String pictureURL;
#JsonProperty("updated_at")
private LocalDateTime updatedAt;
#JsonProperty("user_id")
private String userID;
...
//Getters and Setter and overridden methods.
}
#EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public Auth0JwtAuthenticationProvider auth0JwtAuthenticationProvider() {
return new Auth0JwtAuthenticationProvider();
}
#Override
public void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authenticationProvider(auth0JwtAuthenticationProvider()).authorizeRequests().
....
.oauth2ResourceServer().jwt();
}}

Using CAS server with two different Spring Security authentication flows

I have a spring-boot backend application that authorizes users using our JASIG-CAS server and redirects them to frontend which can now access protected resources from backend. Now I need to add a mobile client. Up until now my configuration had SimpleUrlAuthenticationSuccessHandler with hardcoded url of my frontend in CasAuthenticationFilter like so:
public CasAuthenticationFilter casAuthenticationFilter() throws Exception {
CasAuthenticationFilter authenticationFilter = new CasAuthenticationFilter(); authenticationFilter.setAuthenticationManager(authenticationManager());
authenticationFilter.setServiceProperties(serviceProperties());
authenticationFilter.setFilterProcessesUrl("/auth/cas");
SimpleUrlAuthenticationSuccessHandler simpleUrlAuthenticationSuccessHandler =
new SimpleUrlAuthenticationSuccessHandler(env.getRequiredProperty(CAS_REDIRECT_TARGET));
authenticationFilter.setAuthenticationSuccessHandler(simpleUrlAuthenticationSuccessHandler);
return authenticationFilter;
}//CasAuthenticationFilter
But now my mobile client should open browser, show familiar CAS login page, authenticate user, redirect to backend which will issue a deep-link to mobile application. The problem is the hardcoded redirection target which points to frontend. The request from CAS looks the same regardles if it was triggerd from frontend or mobile because both use browsers, so I can't distinguish them using my own AuthenticationSuccessHandler. In a desperate act I tried constructing two different authentication flows using using the same CAS server but different callback endpoints. Here is this monster:
package com.my.company.config;
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.jasig.cas.client.validation.Cas20ServiceTicketValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken;
import org.springframework.security.cas.authentication.CasAuthenticationProvider;
import org.springframework.security.cas.authentication.NullStatelessTicketCache;
import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
import org.springframework.security.cas.web.CasAuthenticationFilter;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
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.DefaultSecurityFilterChain;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.inject.Inject;
import javax.servlet.*;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final Logger log = LoggerFactory.getLogger(SecurityConfiguration.class);
private static final String CAS_URL_SERVER = "cas.url.server";
private static final String CAS_URL_LOGIN = "cas.url.login";
private static final String CAS_URL_LOGOUT = "cas.url.logout";
private static final String CAS_URL_SERVICE = "cas.url.service";
private static final String CAS_URL_CALLBACK = "cas.url.callback";
private static final String CAS_REDIRECT_TARGET = "cas.redirect.target";
#Inject
private Environment env;
#Inject
private AjaxAuthenticationSuccessHandler ajaxAuthenticationSuccessHandler;
#Inject
private AjaxAuthenticationFailureHandler ajaxAuthenticationFailureHandler;
#Inject
#Qualifier("casUserDetailsService")
private AuthenticationUserDetailsService<CasAssertionAuthenticationToken> casAuthenticationUserDetailsService;
#Inject
#Qualifier("formUserDetailsService")
private UserDetailsService userDetailsService;
#Inject
private Http401UnauthorizedEntryPoint authenticationEntryPoint;
#Bean
public Cas20ServiceTicketValidator cas20ServiceTicketValidator() {
return new Cas20ServiceTicketValidator(env.getRequiredProperty(CAS_URL_SERVER));
}
#Bean(name="webAuthProvider")
public CasAuthenticationProvider webCasAuthenticationProvider() {
CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider();
casAuthenticationProvider.setTicketValidator(cas20ServiceTicketValidator());
casAuthenticationProvider.setStatelessTicketCache(new NullStatelessTicketCache());
casAuthenticationProvider.setKey("CAS_WEB_AUTHENTICATION_PROVIDER");
casAuthenticationProvider.setAuthenticationUserDetailsService(casAuthenticationUserDetailsService);
casAuthenticationProvider.setMessageSource(new SpringSecurityMessageSource());
casAuthenticationProvider.setServiceProperties(webServiceProperties());
return casAuthenticationProvider;
}//CasAuthenticationProvider
#Bean(name="mobileAuthProvider")
public CasAuthenticationProvider mobileCasAuthenticationProvider() {
CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider();
casAuthenticationProvider.setTicketValidator(cas20ServiceTicketValidator());
casAuthenticationProvider.setStatelessTicketCache(new NullStatelessTicketCache());
casAuthenticationProvider.setKey("CAS_MOBILE_AUTHENTICATION_PROVIDER");
casAuthenticationProvider.setAuthenticationUserDetailsService(casAuthenticationUserDetailsService);
casAuthenticationProvider.setMessageSource(new SpringSecurityMessageSource());
casAuthenticationProvider.setServiceProperties(mobileServiceProperties());
return casAuthenticationProvider;
}//CasAuthenticationProvider
#Bean
public SingleSignOutFilter singleSignOutFilter() {
SingleSignOutFilter filter = new SingleSignOutFilter();
return filter;
}//SingleSignOutFilter
#Inject
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder())
.and()
.authenticationProvider(mobileCasAuthenticationProvider())
.authenticationProvider(webCasAuthenticationProvider());
}
#Bean(name = "webCasFilter")
public CasAuthenticationFilter webCasAuthenticationFilter() throws Exception {
CasAuthenticationFilter authenticationFilter = new CasAuthenticationFilter();
authenticationFilter.setBeanName("webCasFilter");
authenticationFilter.setAuthenticationManager(authenticationManager());
authenticationFilter.setServiceProperties(webServiceProperties());
authenticationFilter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/auth/cas"));
//authenticationFilter.setFilterProcessesUrl("/auth/cas");
SimpleUrlAuthenticationSuccessHandler simpleUrlAuthenticationSuccessHandler =
new SimpleUrlAuthenticationSuccessHandler(env.getRequiredProperty(CAS_REDIRECT_TARGET));
authenticationFilter.setAuthenticationSuccessHandler(simpleUrlAuthenticationSuccessHandler);
return authenticationFilter;
}//CasAuthenticationFilter
#Bean(name = "mobileCasFilter")
public CasAuthenticationFilter mobileCasAuthenticationFilter() throws Exception {
CasAuthenticationFilter authenticationFilter = new CasAuthenticationFilter();
authenticationFilter.setBeanName("mobileCasFilter");
authenticationFilter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/auth/cas/mobile"));
authenticationFilter.setAuthenticationManager(authenticationManager());
authenticationFilter.setServiceProperties(mobileServiceProperties());
//authenticationFilter.setFilterProcessesUrl("/auth/cas/mobile");
SimpleUrlAuthenticationSuccessHandler simpleUrlAuthenticationSuccessHandler =
new SimpleUrlAuthenticationSuccessHandler("/mobile/deep-link");
authenticationFilter.setAuthenticationSuccessHandler(simpleUrlAuthenticationSuccessHandler);
return authenticationFilter;
}//CasAuthenticationFilter
#Bean(name="webCasAuthenticationEntryPoint")
public CasAuthenticationEntryPoint webCasAuthenticationEntryPoint() {
CasAuthenticationEntryPoint entryPoint = new CasAuthenticationEntryPoint();
entryPoint.setLoginUrl(env.getRequiredProperty(CAS_URL_LOGIN));
entryPoint.setServiceProperties(webServiceProperties());
return entryPoint;
}//CasAuthenticationEntryPoint
#Bean(name="mobileCasAuthenticationEntryPoint")
public CasAuthenticationEntryPoint mobileCasAuthenticationEntryPoint() {
CasAuthenticationEntryPoint entryPoint = new CasAuthenticationEntryPoint();
entryPoint.setLoginUrl(env.getRequiredProperty(CAS_URL_LOGIN));
entryPoint.setServiceProperties(mobileServiceProperties());
return entryPoint;
}//CasAuthenticationEntryPoint
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/scripts/**/*.{js,html}")
.antMatchers("/bower_components/**")
.antMatchers("/i18n/**")
.antMatchers("/assets/**")
.antMatchers("/swagger-ui/**")
.antMatchers("/test/**")
.antMatchers("/console/**");
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
private class CasRedirectionFilter implements Filter {
public void init(FilterConfig fConfig) throws ServletException {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
HttpServletResponse res = (HttpServletResponse) response;
//CasAuthenticationEntryPoint caep = casAuthenticationEntryPoint();
res.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
HttpServletRequest req = (HttpServletRequest) request;;
String contextPath = req.getRequestURI();
if(contextPath.equals("/api/login/mobile")){
String redirectUrl = "https://cas.server.com/cas/login?service=http://localhost:8080/auth/cas/mobile";
res.setHeader("Location", redirectUrl);
}else {
String redirectUrl = "https://cas.server.com/cas/login?service=http://localhost:8080/auth/cas";
res.setHeader("Location", redirectUrl);
}
}
public void destroy() {
}
}
#Bean
public FilterChainProxy loginFilter() throws Exception {
List<SecurityFilterChain> chains = new ArrayList<SecurityFilterChain>();
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/api/login/cas"), new CasRedirectionFilter()));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/api/login/mobile"), new CasRedirectionFilter()));
log.debug("loginFilter {}", chains);
return new FilterChainProxy(chains);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
.and()
.csrf()
.disable()
.addFilterBefore(mobileCasAuthenticationFilter(),CasAuthenticationFilter.class)
.addFilterBefore(webCasAuthenticationFilter(),CasAuthenticationFilter.class)
.addFilterBefore(singleSignOutFilter(), CasAuthenticationFilter.class)
.addFilter(loginFilter())
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.logout()
.logoutUrl("/api/logout")
.clearAuthentication(true)
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.logoutSuccessUrl(env.getRequiredProperty(CAS_URL_LOGOUT))
.permitAll()
.and()
.headers()
.frameOptions()
.disable()
.and()
.formLogin()
//.defaultSuccessUrl(env.getRequiredProperty(CAS_REDIRECT_TARGET), true)
.successHandler(ajaxAuthenticationSuccessHandler)
.failureHandler(ajaxAuthenticationFailureHandler)
.loginProcessingUrl("/api/authentication")
.usernameParameter("j_username")
.passwordParameter("j_password")
.permitAll()
.and()
.authorizeRequests()
.antMatchers(org.springframework.http.HttpMethod.OPTIONS, "/api/**").permitAll()
.antMatchers(org.springframework.http.HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/app/**").authenticated()
.antMatchers(HttpMethod.GET, "/api/login").authenticated()
.antMatchers("/api/login/mobile").authenticated()
.antMatchers("/api/login/cas").authenticated()
.antMatchers("/api/register").permitAll()
.antMatchers("/api/activate").permitAll()
.antMatchers("/api/authenticate").authenticated()
.antMatchers("/api/logs/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/api/**").authenticated()
.antMatchers("/metrics/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/mobile/**").permitAll()
.antMatchers("/health/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/trace/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/dump/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/shutdown/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/beans/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/configprops/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/info/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/autoconfig/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/env/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/trace/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/api-docs/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/protected/**").authenticated();
}
#EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
private static class GlobalSecurityConfiguration extends GlobalMethodSecurityConfiguration {
#Inject
ConferenceRepository conferenceRepository;
#Inject
UserRepository userRepository;
public GlobalSecurityConfiguration() {
super();
}
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
PermissionChecker permissionEvaluator = new PermissionChecker(conferenceRepository, userRepository);
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(permissionEvaluator);
return expressionHandler;
}
}
#Bean(name="webServiceProperties")
public ServiceProperties webServiceProperties() {
ServiceProperties serviceProperties = new ServiceProperties();
serviceProperties.setService("http://localhost:8080/auth/cas");
serviceProperties.setSendRenew(true);
serviceProperties.setAuthenticateAllArtifacts(true);
return serviceProperties;
}//serviceProperties
#Bean(name="mobileServiceProperties")
public ServiceProperties mobileServiceProperties() {
ServiceProperties serviceProperties = new ServiceProperties();
serviceProperties.setService("http://localhost:8080/auth/cas/mobile");
serviceProperties.setSendRenew(true);
serviceProperties.setAuthenticateAllArtifacts(true);
return serviceProperties;
}//serviceProperties
}
This works to some degree. When mobile authentication flow is issued it works as intended but when frontend issues /api/login/cas the TicketGrantingTicket from CAS is first checked using mobile filter against service=/auth/cas/mobile but was issued for service=/auth/cas which invalidates TGT and subsequent validation using casWebAuthenticationFilter uses that invalidated ticket which of course.
So now I'm out of ideas how to force CasAuthenticationFilter to process only certain tickets? Perhaps I'm so tangled up in my idea that I can't see simpler solution? Maybe I should do two separate http security configs?
EDIT:
It seems that it all boils down to the order in which I put AuthenticationProvider:
#Inject
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder())
.and()
.authenticationProvider(webCasAuthenticationProvider())
.authenticationProvider(mobileCasAuthenticationProvider());
}
When mobileAuthenticationProider() goes first then the mobile login works and web one doesn't when I switch the order in which they are called then mobile authentication fails and the web one starts to work.
Ok so I got it working, it doesn't look like the best, most robust solution so it probably nees some more investigation and care. Nevertheless here it goes:
#Bean
public AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> authenticationDetailsSource() {
return new AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails>() {
#Override
public WebAuthenticationDetails buildDetails(
HttpServletRequest request) {
return new CustomAuthenticationDetails(request);
}
};
}
#Bean
public AuthenticationProvider customAuthenticationProvider() {
return new AuthenticationProvider() {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String serviceUrl;
serviceUrl = ((CustomAuthenticationDetails) authentication.getDetails()).getURI();
if (serviceUrl.equals(env.getRequiredProperty(CAS_URL_CALLBACK_MOBILE))) {
return mobileCasAuthenticationProvider().authenticate(authentication);
} else {
return webCasAuthenticationProvider().authenticate(authentication);
}
}
public boolean supports(final Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class
.isAssignableFrom(authentication))
|| (CasAuthenticationToken.class.isAssignableFrom(authentication))
|| (CasAssertionAuthenticationToken.class
.isAssignableFrom(authentication));
}
};
}
I added my custom AuthenticationProvider which distinguishes between the two that I really needed. It does it using another custom class, namely CustomAuthenticationDetails which stores information about where the request came from.
public class CustomAuthenticationDetails extends WebAuthenticationDetails {
private final Logger log = LoggerFactory.getLogger(CustomAuthenticationDetails.class);
private final String URI;
private final String sessionId;
public CustomAuthenticationDetails(HttpServletRequest request) {
super(request);
this.URI = request.getRequestURI();
HttpSession session = request.getSession(false);
this.sessionId = (session != null) ? session.getId() : null;
}
public String getURI() {
return URI;
}
public String getSessionId() {
return sessionId;
}
}
And all this is wired together in AuthenticationFilter using authenticationFilter.setAuthenticationDetailsSource(authenticationDetailsSource());. Hope it can help someone in future problems or at least lead in the right direction.

How to configure waffle in spring using java configuration

I have been struggling to get waffle to work with spring 4.2.5 using spring java configuration. And I thought I might as well help others in the same situation.
We use a custom preWaffle and postWaffle filter to authenticate that the user exists in our database after it has been validated via waffles NTLM protocol.
We also have methods for authorization of a user actions using the EnableGlobalMethodSecurity annotation.
To get this working in spring java configuration was trouble some to say the least. You can find our solution in the answer below. I hope it will help.
SpringConfiguration.java
// ... imports
#Configuration
#EnableWebMvc
#EnableScheduling
#PropertySources({
#PropertySource("classpath:app.properties")
// ... Properties sources
})
#EnableTransactionManagement
#ComponentScan(basePackages = "com.our.package")
public class SpringConfiguration extends WebMvcConfigurerAdapter {
// Our Spring configuration ...
}
SecurityConfiguration.java
// ... imports
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, proxyTargetClass = true)
#Order(1)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
// Authentication manager configuration
#Autowired
private WindowsAuthenticationProviderWrapper authProvider;
#Autowired
private AuthenticationManagerBuilder auth;
#Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider);
}
#Bean
public AuthenticationManager authenticationManager() throws Exception {
return auth.getObject();
}
// Waffle configuration
#Bean
public Filter customPreAuthSecurityFilter() {
return new CustomPreAuthSecurityFilter();
}
#Bean
public Filter customNegotiateSecurityFilter() {
return new CustomNegotiateSecurityFilter();
}
#Bean
public WindowsAuthProviderImpl waffleAuthProvider(){
return new WindowsAuthProviderImpl();
}
#Bean(name="negotiateSecurityFilterProvider")
#Autowired
public NegotiateSecurityFilterProvider negotiateSecurityFilterProvider(){
NegotiateSecurityFilterProvider bean = new NegotiateSecurityFilterProvider(waffleAuthProvider());
List<String> protocols = new ArrayList<>();
protocols.add("Negotiate");
bean.setProtocols(protocols);
return bean;
}
#Bean
public BasicSecurityFilterProvider basicSecurityFilterProvider(){
return new BasicSecurityFilterProvider(waffleAuthProvider());
}
#Bean(name="waffleSecurityFilterProviderCollection")
#Autowired
public waffle.servlet.spi.SecurityFilterProviderCollection negotiateSecurityFilterProviderCollection() {
final List<SecurityFilterProvider> lsp = new ArrayList<>();
lsp.add(negotiateSecurityFilterProvider());
lsp.add(basicSecurityFilterProvider());
return new waffle.servlet.spi.SecurityFilterProviderCollection(lsp.toArray(new SecurityFilterProvider[]{}));
}
#Bean(name="negotiateSecurityFilterEntryPoint")
#Autowired
public waffle.spring.NegotiateSecurityFilterEntryPoint negotiateSecurityFilterEntryPoint() {
final waffle.spring.NegotiateSecurityFilterEntryPoint ep = new waffle.spring.NegotiateSecurityFilterEntryPoint();
ep.setProvider(negotiateSecurityFilterProviderCollection());
return ep;
}
#Bean(name="negotiateSecurityFilter")
#Autowired
public waffle.spring.NegotiateSecurityFilter waffleNegotiateSecurityFilter(){
waffle.spring.NegotiateSecurityFilter bean = new waffle.spring.NegotiateSecurityFilter();
bean.setRoleFormat("both");
bean.setPrincipalFormat("fqn");
bean.setAllowGuestLogin(false);
bean.setProvider(negotiateSecurityFilterProviderCollection());
return bean;
}
// Static Mappings
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/assets/**");
}
// Security filter chain
// The custom filters can be removed if you only use waffle
// but this is how we added them
#Override
protected void configure(HttpSecurity http) throws Exception {
// A user needs to have the role user and has to be authenticated
http.exceptionHandling()
.authenticationEntryPoint(negotiateSecurityFilterEntryPoint()).and()
.addFilterBefore(customPreAuthSecurityFilter(), BasicAuthenticationFilter.class)
.addFilterAfter(waffleNegotiateSecurityFilter(), BasicAuthenticationFilter.class)
.addFilterAfter(customNegotiateSecurityFilter(), BasicAuthenticationFilter.class)
.authorizeRequests().anyRequest().fullyAuthenticated();
}
}
To be able to autowire the waffle authProvider I created the following wrapperclass.
WindowsAuthenticationProviderWrapper.java
// ... imports
// This class purpose is only to make the Windows authentication provider autowireable in spring.
#Component
public class WindowsAuthenticationProviderWrapper extends WindowsAuthenticationProvider{}
As requested (Some code has been stripped due to security risks).
CustomPreAuthFilter.java
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* This filter removes the excess negoatiate header sent by IE. If the client
* has already authenticated, strip the Authorization header from the request.
*/
public class CustomPreAuthSecurityFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
SecurityContext sec = SecurityContextHolder.getContext();
HttpServletRequest req = (HttpServletRequest) servletRequest;
if(sec != null && sec.getAuthentication() != null) {
req = new CustomServletRequestWrapper(req);
}
try {
filterChain.doFilter(req, servletResponse);
} catch (RuntimeException e) {
sendUnauthorized((HttpServletResponse) servletResponse);
}
}
private void sendUnauthorized(HttpServletResponse response) throws IOException {
logger.warn("error logging in user");
SecurityContextHolder.clearContext();
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}
CustomNegotiateSecurityFilter.java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;
import waffle.servlet.WindowsPrincipal;
import waffle.spring.WindowsAuthenticationToken;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;
/**
* Handle post NTLM authentication against system database
*/
public class CustomNegotiateSecurityFilter extends GenericFilterBean {
#Autowired
private UserDAO userDAO;
#Autowired
Environment env;
private static final Logger LOGGER = LoggerFactory.getLogger(CustomNegotiateSecurityFilter.class);
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) req;
final HttpServletResponse response = (HttpServletResponse) res;
SecurityContext sec = SecurityContextHolder.getContext();
Authentication authentication = sec.getAuthentication();
// Continue filter chain if we are anonymously authenticated or if DB authentication has already happened.
if (authentication != null && authentication.getClass() == WindowsAuthenticationToken.class) {
// The user is Authenticated with NTLM but needs to be checked against the DB.
User user;
try {
// fetch user from DB ...
} catch (Exception e) {
// The could not be found in the DB.
sendUnauthorized(response);
return;
}
// The user was found in the DB.
WindowsPrincipal principal = (WindowsPrincipal)authentication.getPrincipal();
final CustomAuthenticationToken token = new CustomAuthenticationToken(principal); // This class extends WindowsAuthenticationToken
// add roles to token ...
sec.setAuthentication(token);
}
chain.doFilter(request, response);
}
private void sendUnauthorized(HttpServletResponse response) throws IOException {
logger.warn("Could not log in user");
SecurityContextHolder.clearContext();
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
private void addRoleToAuthentication(WindowsAuthenticationToken authentication, String role) {
for(GrantedAuthority authority : authentication.getAuthorities()) {
if(authority.getAuthority().equals(role)) {
return;
}
}
authentication.getAuthorities().add(new SimpleGrantedAuthority(role));
}
}
EDIT
For those who asked about here is one implementation. CustomServletRequestWrapper:
class CustomServletRequestWrapper extends HttpServletRequestWrapper {
public CustomServletRequestWrapper(HttpServletRequest request) {
super(request);
}
public String getHeader(String name) {
if(name.equals("Authorization"))
return null;
String header = super.getHeader(name);
return (header != null) ? header : super.getParameter(name); // Note: you can't use getParameterValues() here.
}
public Enumeration getHeaderNames() {
List<String> names = Collections.list(super.getHeaderNames());
names.addAll(Collections.list(super.getParameterNames()));
names.remove("Authorization");
return Collections.enumeration(names);
}
}
If you need more information don't hessitate to ask.

Categories