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
Related
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
I have a project using spring boot, spring security with oauth2. When I use
SecurityContextHolder.getContext().getAuthentication().getPrincipal()
this method returns only username end in the examples that I see this method returns UserDetails implentation.
Follow the settings
OAuthSecurityConfig.java:
package br.com.altha.api.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.Order;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import br.com.altha.api.security.CustomUserDetailsService;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled=true)
#EnableAuthorizationServer
#EnableResourceServer
#Order(SecurityProperties.BASIC_AUTH_ORDER-2)
#Import(Encoders.class)
public class OAuthSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private PasswordEncoder userPasswordEncoder;
#Autowired
private CustomUserDetailsService userDetailsService;
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(userPasswordEncoder);
}
}
AuthorizationServerConfig.java:
package br.com.altha.api.config;
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.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import br.com.altha.api.security.CustomUserDetailsService;
#Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private static final String SECRET = "PASSWORD";
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private PasswordEncoder oauthClientPasswordEncoder;
#Autowired
private CustomUserDetailsService userDetailsService;
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()").passwordEncoder(oauthClientPasswordEncoder);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("altha-adms")
.secret(oauthClientPasswordEncoder.encode(SECRET))
.scopes("write", "read")
.authorizedGrantTypes("password", "refresh_token")
.accessTokenValiditySeconds(60/*1800*/)
.refreshTokenValiditySeconds(1800);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter())
.authenticationManager(authenticationManager)
.reuseRefreshTokens(false)
.userDetailsService(userDetailsService);
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(SECRET);
return converter;
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
}
ResourceServerConfig.java:
package br.com.altha.api.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler;
import org.springframework.security.oauth2.provider.expression.OAuth2MethodSecurityExpressionHandler;
import br.com.altha.api.handler.RestExceptionHandler;
#Configuration
#Import(Encoders.class)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Bean
public RestExceptionHandler handlerError() {
return new RestExceptionHandler();
}
#Bean
public MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/public/**").permitAll()
.antMatchers("/private/**").authenticated()
.and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.stateless(true);
}
}
I was able to solve this problem with this code:
I added a bean to UserAuthenticationConverter
#Bean
public UserAuthenticationConverter userAuthenticationConverter() {
DefaultUserAuthenticationConverter defaultUserAuthenticationConverter = new DefaultUserAuthenticationConverter();
defaultUserAuthenticationConverter.setUserDetailsService(userDetailsService);
return defaultUserAuthenticationConverter;
}
After this, I set this bean in the JwtAccessTokenConverter
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
final JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setSigningKey(SECRET);
((DefaultAccessTokenConverter) jwtAccessTokenConverter.getAccessTokenConverter())
.setUserTokenConverter(userAuthenticationConverter());
return jwtAccessTokenConverter;
}
The cause would vary based on authentication/authorization technique but in my case I have 2 filters authentication/authorization the issue was passing the username instead of the whole user object to the UsernamePasswordAuthenticationToken in the authorization filter :
return new UsernamePasswordAuthenticationToken(userDetails.getUsername(), .....);
The first constructor argument is being set to Principle object.
So fix was to pass the whole userDetails object
return new UsernamePasswordAuthenticationToken(userDetails, .....);
With Spring Boot 1.5.x, you can implement a PrincipalExtractor and override it's Object extractPrincipal(Map<String, Object> map). The following example component has a UserdetailsService autowired in to lookup the UserDetails object based on the username.
#Component
public class MyPrincipalExtractor implements PrincipalExtractor {
private UserDetailsService userDetailsService;
#Value("${security.oauth2.client.principal-attribute}")
private String principalAttribute;
#Autowired
public MyPrincipalExtractor(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
#Override
public Object extractPrincipal(Map<String, Object> map) {
if (!map.containsKey(principalAttribute)) {
return null;
}
final String username = (String) map.get(principalAttribute);
try {
return userDetailsService.loadUserByUsername(username);
} catch (UsernameNotFoundException e) {
// This may be the first time this user is accessing the system,
// maybe you want to extract some other attributes from the map
// and return a different type of user object that can be used to
// create a new user.
}
}
}
Now the SecurityContextHolder.getContext().getAuthentication().getPrincipal() will contain a UserDetails object.
For a more detailed tutorial see:
https://www.baeldung.com/spring-security-oauth-principal-authorities-extractor
https://docs.spring.io/spring-security-oauth2-boot/docs/current-SNAPSHOT/api/org/springframework/boot/autoconfigure/security/oauth2/resource/PrincipalExtractor.html
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
I am working on a Spring Boot 1.4.1 application (that implements some REST web services) and I am finding some difficulties trying to implement Spring Security into this project.
I have the following situation:
1) I have the CustomUserDetails that implements the Spring Security UserDetails interface:
import com.betrivius.domain.User;
import com.betrivius.domain.UserRole;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
public class CustomUserDetails extends User implements UserDetails {
private static final long serialVersionUID = 1L;
public CustomUserDetails(User user){
super(user);
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
for(UserRole role : this.getUserRoles() ){
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(role.getName());
authorities.add(grantedAuthority);
}
return authorities;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
#Override
public String getUsername() {
return super.getUsername();
}
}
2) Then I have the CustomUserDetailsService implementing the Spring Security UserDetailsService interface:
import com.betrivius.dao.UserDAO;
import com.betrivius.domain.User;
import com.betrivius.security.bean.CustomUserDetails;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
#Transactional
#Service("customUserDetailsService")
public class CustomUserDetailsService implements UserDetailsService{
#Autowired
private UserDAO userDao;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userDao.findByUsername(username);
if(user == null){
throw new UsernameNotFoundException("No user present with username: "+username);
}else{
return new CustomUserDetails(user);
}
}
}
3) Finnally I have the Spring Security confinguration class named WebSecurityConfig extending the WebSecurityConfigurerAdapter:
import com.betrivius.security.service.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
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;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
#Configuration
#EnableWebSecurity
#ComponentScan(basePackageClasses = CustomUserDetailsService.class)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordencoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/Accomodation/**").access("hasRole('ROLE_USER')")
.anyRequest().permitAll()
.and()
.csrf().disable();
/*.and()
.formLogin().loginPage("/login")
.usernameParameter("username").passwordParameter("password")
.and()
.logout().logoutSuccessUrl("/login?logout")
.and()
.exceptionHandling().accessDeniedPage("/403")
.and()
.csrf();*/
}
#Bean(name = "passwordEncoder")
public PasswordEncoder passwordencoder() {
return new BCryptPasswordEncoder();
}
}
As you can see in the previous code snippet in the configuration class I have specified that only a user having the ROLE_USER setted can access to all the resources under the /Accomodation/ path, I have done it by:
.antMatchers("/Accomodation/**").access("hasRole('ROLE_USER')")
4) This is the UserDAO that obtain the user information used into the previous CustomUserDetailsService class:
#Repository
#Transactional(propagation = Propagation.MANDATORY)
public interface UserDAO extends JpaRepository<User, Long> {
User findByUsername(String username);
#Query("SELECT r FROM User u JOIN u.userRoles r where u.username = :username")
List<UserRole> findRoleByUserName(String username);
}
The username and password are stord in the DB and are:
USERNAME: ErSabba
PASSWORD: pswd
User table have a many to many relationship with User_Roles, the role of ErSabba user is ROLE_USER (tried setting USER as role).
So, by PostMan Chrome plugin I perform a GET Request toward the URL:
http://localhost:8080/Accomodation/7
passing the previous credential in this way:
As you can see the problem is that inserting the right username and password into the basic authentication form it give me 403 error Access denied.
I think that it means that I have perform the authentication but I have not the authorization.
The strange thing is that I also tried to put bad credential and I always obtain the same instead of 401 Bad Credential error.
I also have put a breakpoint on this line of the loadUserByUsername() method into the CustomUserDetailsService class (that retrieve the User info).
User user = userDao.findByUsername(username);
Running in debug mode it doesn't stop on this line so it seems that Spring Security configuration doesn't works fine...
What could be the problem? What am I missing? How can I try to fix it?
I would recommend you to do a test without a DB. You can provide an inMemoryconfiguration, like this:
auth.inMemoryAuthentication().withUser("ErSabba").password("pswd").authorities("ROLE_USER");
and try to login then. If it would work, it will show that there is something wrong with credentials in DB.
How do you store password in DB? If it's plain text, it will not work, as you have password encoder, that should read you password as a hash.
Hope that helps.
I am using Spring Security with Spring Boot and authenticate my users via JASIG CAS. Some of the pages require explicitly authentication (.authenticated()) and some of them are for all users.
Now there is a certain area in the menu, which indicates the current user and possible actions, like login/logout.
My main problem is now that the main page is public (permitAll()) and that, if a user already has a CAS session through some other application, he is shown as "anonymousUser" until login is clicked manually or a protected page is opened.
Is there somebody who has any ideas on how to get this working?
My security configuration:
import org.jasig.cas.client.validation.Cas20ServiceTicketValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.cas.authentication.CasAuthenticationProvider;
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.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private Environment env;
#Autowired
private CasAuthenticationProvider authProvider;
#Bean
public ServiceProperties serviceProperties() {
ServiceProperties sp = new ServiceProperties();
sp.setSendRenew(false);
sp.setService(env.getProperty("app.url") + "/j_spring_cas_security_check");
return sp;
}
#SuppressWarnings("rawtypes")
#Autowired
private AuthenticationUserDetailsService customUserDetailsService() {
return new CASUserDetailsService();
}
#Bean
public CasAuthenticationProvider casAuthenticationProvider() {
CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider();
casAuthenticationProvider.setAuthenticationUserDetailsService(customUserDetailsService());
casAuthenticationProvider.setServiceProperties(serviceProperties());
casAuthenticationProvider.setTicketValidator(cas20ServiceTicketValidator());
casAuthenticationProvider.setKey("an_id_for_this_auth_provider_only");
return casAuthenticationProvider;
}
#Bean
public Cas20ServiceTicketValidator cas20ServiceTicketValidator() {
return new Cas20ServiceTicketValidator(env.getProperty("cas.service.url"));
}
#Bean
public CasAuthenticationFilter casAuthenticationFilter() throws Exception {
CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter();
casAuthenticationFilter.setAuthenticationManager(authenticationManager());
casAuthenticationFilter.setAuthenticationSuccessHandler(savedRequestAwareAuthenticationSuccessHandler());
return casAuthenticationFilter;
}
#Bean
public CasAuthenticationEntryPoint casAuthenticationEntryPoint() {
CasAuthenticationEntryPoint ep = new CasAuthenticationEntryPoint();
ep.setLoginUrl(env.getProperty("cas.service.url") + "/login");
ep.setServiceProperties(serviceProperties());
return ep;
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/js/**").antMatchers("/fonts/**").antMatchers("/images/**").antMatchers("/css/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling().
authenticationEntryPoint(casAuthenticationEntryPoint()).and().addFilter(casAuthenticationFilter()).
logout().logoutUrl("/caslogout").addLogoutHandler(logoutHandler()).logoutSuccessUrl("/").deleteCookies("JSESSIONID").permitAll().and().
csrf().disable().headers().frameOptions().disable().authorizeRequests().antMatchers("/rest/**").permitAll().
antMatchers("/login/**").authenticated().antMatchers("/settings/**").authenticated().
antMatchers("/projects/*/settings").authenticated().antMatchers("/projects/*/role").authenticated().
antMatchers("/projects/*/*/admin").authenticated().antMatchers("/**").permitAll();
}
#Bean
public SavedRequestAwareAuthenticationSuccessHandler savedRequestAwareAuthenticationSuccessHandler() {
CASAuthSuccessHandler auth = new CASAuthSuccessHandler();
return auth;
}
#Bean
public CASLogoutHandler logoutHandler() {
CASLogoutHandler logout = new CASLogoutHandler();
return logout;
}
#EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
private static class GlobalSecurityConfiguration extends GlobalMethodSecurityConfiguration {
}
}
What you are looking for is CAS's Gateway feature. Currently this is not supported in Spring Security. There is a JIRA logged to support it and a Pull Request that is waiting additional modifications based on my feedback to the submitter.
I'd take a look at the Pull Request as it demonstrates a few options on how to implement this. Please do read through the whole thing as you will need to make some changes to the Pull Request to ensure your application performs.