Spring boot + LDAP form login, logout and validating the token after login - spring-boot

I am integrating spring boot with LDAP to Authenticate a user. So that only authenticated users can only access the API.
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
LdapAuthenticationProvider authenticationProvider(final LdapAuthenticator authenticator) {
return new LdapAuthenticationProvider(authenticator);
}
#Bean
BindAuthenticator authenticator(final BaseLdapPathContextSource contextSource) {
final BindAuthenticator authenticator = new BindAuthenticator(contextSource);
authenticator.setUserDnPatterns(new String[] {
"xx" });
return authenticator;
}
#Override
public void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication().userSearchFilter("(sAMAccountName={0})")
.contextSource(contextSource());
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.formLogin().and()
.authorizeRequests()
.anyRequest().fullyAuthenticated().and().logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login");
}
#Override
public void configure(final WebSecurity web) throws Exception {
web.debug(true);
}
#Bean
LdapContextSource contextSource() {
final LdapContextSource contextSource = new LdapContextSource();
contextSource.setUrl("xx);
contextSource.setBase("xx");
contextSource.setUserDn("xx");
contextSource.setPassword("xx");
contextSource.setPooled(true);
contextSource.afterPropertiesSet();
return contextSource;
}
#Bean
public LdapTemplate ldapTemplate() {
final LdapTemplate ldapTemplate = new LdapTemplate(
contextSource());
ldapTemplate.setIgnorePartialResultException(true);
return ldapTemplate;
}
}
I am using the inbuild form login.
Question
Who (which class) is responsible to create a success token and where is it stored and in successive calls how is it validated?
Now I am only redirecting the unauthenticated calls to the login page due to this it giving 200 success responses, How to override this and send 401
Edited:
I have one specific question
If there is no token, the user is stored in the session -> how subsequent requests are validated. Which all classes are used

Related

spring boot API + security +ldap

I want to achieve LDAP verification without form login, But not sure how to achieve it.
EDITED: I have a custom POST API /login which accept userId and password and validate it and creates a session cookies/Token.
Few doubts:
how to point to custom login URL endpoint?
How internally the form login used to validate the cookie once we successfully validated the credentials? How to achieve the same with custom login endpoint?
Do i have to change something in CRSF?
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private LdapProperties ldapProperties;
#Bean
public LdapAuthenticationProvider
authenticationProvider(final LdapAuthenticator authenticator) {
return new LdapAuthenticationProvider(authenticator);
}
#Bean
public BindAuthenticator authenticator(final BaseLdapPathContextSource contextSource) {
final BindAuthenticator authenticator = new BindAuthenticator(contextSource);
authenticator.setUserDnPatterns(new String[] {
ldapProperties.getUserDnPatterns() });
return authenticator;
}
#Override
public void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication().userSearchFilter(ldapProperties.getSearchFilter())
.contextSource(contextSource());
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.formLogin().and()
.authorizeRequests()
.anyRequest().fullyAuthenticated().and().logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login");
}
#Override
public void configure(final WebSecurity web) throws Exception {
web.debug(true);
}
#Bean
public LdapContextSource contextSource() {
final LdapContextSource contextSource = new LdapContextSource();
contextSource.setUrl(ldapProperties.getUrl());
contextSource.setBase(ldapProperties.getBase());
contextSource.setUserDn(ldapProperties.getUserDn());
contextSource.setPassword(ldapProperties.getUserDnPaswrd());
contextSource.setPooled(true);
contextSource.afterPropertiesSet();
return contextSource;
}
#Bean
public LdapTemplate ldapTemplate() {
final LdapTemplate ldapTemplate = new LdapTemplate(
contextSource());
ldapTemplate.setIgnorePartialResultException(true);
return ldapTemplate;
}
}

Spring Boot with Spring Security - Two Factor Authentication with SMS/ PIN/ TOTP

I'm working on a Spring Boot 2.5.0 web application with Spring Security form login using Thymeleaf. I'm looking for ideas on how to implement two factor authentication (2FA) with spring security form login.
The requirement is that when a user logs in with his username and password via. the login form, if the username and password authentication is successful an SMS code should be sent to the registered mobile number of the user and he should be challenged with another page to enter the SMS code. If user gets the SMS code correctly, he should be forwarded to the secured application page.
On the login form, along with the username and password, the user is also requested to enter the text from a captcha image which is verified using a SimpleAuthenticationFilter which extends UsernamePasswordAuthenticationFilter.
This is the current SecurityConfiguration
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsServiceImpl userDetailsService;
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.addFilterBefore(authenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers(
"/favicon.ico",
"/webjars/**",
"/images/**",
"/css/**",
"/js/**",
"/login/**",
"/captcha/**",
"/public/**",
"/user/**").permitAll()
.anyRequest().authenticated()
.and().formLogin()
.loginPage("/login")
.permitAll()
.defaultSuccessUrl("/", true)
.and().logout()
.invalidateHttpSession(true)
.clearAuthentication(true)
.deleteCookies("JSESSONID")
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login?logout")
.permitAll()
.and().headers().frameOptions().sameOrigin()
.and().sessionManagement()
.maximumSessions(5)
.sessionRegistry(sessionRegistry())
.expiredUrl("/login?error=5");
}
public SimpleAuthenticationFilter authenticationFilter() throws Exception {
SimpleAuthenticationFilter filter = new SimpleAuthenticationFilter();
filter.setAuthenticationManager(authenticationManagerBean());
filter.setAuthenticationFailureHandler(authenticationFailureHandler());
return filter;
}
#Bean
public AuthenticationFailureHandler authenticationFailureHandler() {
return new CustomAuthenticationFailureHandler();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider auth = new DaoAuthenticationProvider();
auth.setUserDetailsService(userDetailsService);
auth.setPasswordEncoder(passwordEncoder());
return auth;
}
/** TO-GET-SESSIONS-STORED-ON-SERVER */
#Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
}
And this is the SimpleAuthenticationFilter mentioned above.
public class SimpleAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
public static final String SPRING_SECURITY_FORM_CAPTCHA_KEY = "captcha";
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
HttpSession session = request.getSession(true);
String captchaFromSession = null;
if (session.getAttribute("captcha") != null) {
captchaFromSession = session.getAttribute("captcha").toString();
} else {
throw new CredentialsExpiredException("INVALID SESSION");
}
String captchaFromRequest = obtainCaptcha(request);
if (captchaFromRequest == null) {
throw new AuthenticationCredentialsNotFoundException("INVALID CAPTCHA");
}
if (!captchaFromRequest.equals(captchaFromSession)) {
throw new AuthenticationCredentialsNotFoundException("INVALID CAPTCHA");
}
UsernamePasswordAuthenticationToken authRequest = getAuthRequest(request);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
private UsernamePasswordAuthenticationToken getAuthRequest(HttpServletRequest request) {
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
return new UsernamePasswordAuthenticationToken(username, password);
}
private String obtainCaptcha(HttpServletRequest request) {
return request.getParameter(SPRING_SECURITY_FORM_CAPTCHA_KEY);
}
}
Any ideas on how to approach this ? Thanks in advance.
Spring Security has an mfa sample to get you started. It uses Google Authenticator with an OTP, but you can plug in sending/verifying your SMS short-code instead.
You might also consider keeping the captcha verification separate from the (out of the box) authentication filter. If they are separate filters in the same filter chain, it will have the same effect with less code.

Oauth Spring Security not unauthorized to microservice oauth, registerController

I am beginner to Spring. I've been with this problem for several days and I wouldn't know how to solve it.
I have two microservices:
Microservice authentication : This allows return a JWT token to user when it access to path: /oauth/token
Microservice Account user : This microservice will have a lot of functions but the problem is it:
The user must be register in platform and this microservice call to server oauth to save the new created user.
So, I create the controller to microservice Oauth:
#PostMapping
#PreAuthorize("#oauth2.hasScope('server')")
public UserDto createUser(#Valid #RequestBody UserDto userDto) {
Usuario savedUser = new Usuario();
try {
savedUser = userService.create(this.toUsuario(userDto));
} catch (ArendheException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return toDto(savedUser);
}
The WebSecurityConfigureAdapter is:
#Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService usuarioService;
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
#Autowired
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(this.usuarioService).passwordEncoder(passwordEncoder());
}
#Bean("authenticationManager")
#Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(HttpMethod.POST,"/oauth/**").permitAll()
.antMatchers(HttpMethod.POST, "/user/**").permitAll()
.anyRequest().authenticated()
.and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
As you can see, I write two antMatchers, the second to create a new user.
The problem is when I test it with Postman (path localhost:8005/user with data JSON parsed to POST method). The output is:
{
"error": "unauthorized",
"error_description": "Full authentication is required to access this resource"
}
I dont understand it because I have a permitAll to /user/** path.
Thanks.

Spring Security SessionRegistry returning empty list

Friends I have search a lot and try every solution available on the internet, but my problem not solved.
I want check (in Spring Boot web application) currently user logged in with the credentials specify in the login page, if there is a session with username currently, then invalidate that first a login again for the request.
I want to ensure there will be one session for the user, if session exist then invalidate and login forcefully.
I am trying to get all the principle from the SessionRegistry, but it always returning [] empty list even after multiple user logged in the system.
Here is my spring security config
...
#Autowired private CustomUserDetailsService customUserDetailsService;
#Autowired private CustomUrlAuthenticationSuccessHandler customUrlAuthenticationSuccessHandler;
#Autowired private PasswordEncoder passwordEncoder;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider());
}
#Bean
JCaptchaAuthenticationFilter jCaptchaAuthenticationFilter() throws Exception {
JCaptchaAuthenticationFilter jCaptchaAuthenticationFilter = new JCaptchaAuthenticationFilter();
jCaptchaAuthenticationFilter.setAuthenticationManager(authenticationManager());
jCaptchaAuthenticationFilter.setAuthenticationFailureHandler(customLoginFailureHandler());
jCaptchaAuthenticationFilter.setAuthenticationSuccessHandler(customUrlAuthenticationSuccessHandler);
return jCaptchaAuthenticationFilter;
}
#Bean
public DaoAuthenticationProvider authProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(customUserDetailsService);
authProvider.setPasswordEncoder(passwordEncoder);
return authProvider;
}
#Bean
public CustomLoginFailureHandler customLoginFailureHandler() {
CustomLoginFailureHandler customLoginFailureHandler = new CustomLoginFailureHandler("/login");
return customLoginFailureHandler;
}
#Override
protected void configure(HttpSecurity http) throws Exception {// #formatter:off
http.authorizeRequests().antMatchers("/js/**", "/fonts/**", "/css/**", "/images/**", "/favicon.ico").permitAll()
.antMatchers("/", "/register/**", "/email/**", "/captcha.png/**").permitAll().antMatchers("/login/**")
.permitAll()// Basically I'm allowing parameters for login so
// .antMatchers("/services/**").permitAll()
.antMatchers("/forgot/password/**", "/user/verify/**").permitAll().antMatchers("/user/resetPassword*")
.hasAuthority("CHANGE_PASSWORD_PRIVILEGE").anyRequest().authenticated().and()
.addFilterBefore(jCaptchaAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class).formLogin()
.loginPage("/login").permitAll().and()
.csrf().disable()
.sessionManagement().invalidSessionUrl("/invalidSession")
.maximumSessions(1)
.maxSessionsPreventsLogin(true)
.sessionRegistry(sessionRegistry()).and()
.sessionFixation().newSession().and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/")
.invalidateHttpSession(false).deleteCookies("JSESSIONID").permitAll();
http.headers().frameOptions().sameOrigin();
}
....
#Bean
SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
#Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
Here is i am getting sessions list
List<String> userList = sessionRegistry.getAllPrincipals().stream()
.filter(u -> !sessionRegistry.getAllSessions(u, true).isEmpty())
.map(Object::toString)
.collect(Collectors.toList());
But above code always return empty list. I have checked is there any double sessionRegistry loading by disabling sessionRepositry, but spring throw exception that bean not found.
Please help friends.

Spring - Add a check for 3rd parameter during authentication

At a glance, I have API Back-end App written in Spring Boot which uses JWT for secured data transmission. I want to add 3rd parameter for authorization, so I should have login, password and storeID parameters. I am inspired by this answer How implement Spring security when login page having more field apart from user name and password? but when I followed proposed solution my 3rd parameter in not used. My impression is that I am missing something important in Security Config. Could you please point to my mistake?
SecurityConfig
#SuppressWarnings("SpringJavaAutowiringInspection")
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private AuthenticationDetailsSource<HttpServletRequest, ?> webAuthenticationDetailsSourceImpl;
#Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.authenticationProvider(myAuthProvider());
}
#Bean
public CustomUserDetailsAuthenticationProvider myAuthProvider() throws Exception {
CustomUserDetailsAuthenticationProvider provider = new CustomUserDetailsAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder());
provider.setUserDetailsService(userDetailsService);
return provider;
}
#Bean
public UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter() throws Exception {
UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter = new UsernamePasswordAuthenticationFilter();
usernamePasswordAuthenticationFilter.setAuthenticationManager(authenticationManager());
usernamePasswordAuthenticationFilter.setAuthenticationDetailsSource(webAuthenticationDetailsSourceImpl);
return usernamePasswordAuthenticationFilter;
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
JwtAuthenticationTokenFilter authenticationTokenFilter = new JwtAuthenticationTokenFilter();
authenticationTokenFilter.setAuthenticationManager(authenticationManagerBean());
return authenticationTokenFilter;
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// we don't need CSRF because our token is invulnerable
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// don't create session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
// allow anonymous resource requests
.antMatchers(
HttpMethod.GET,
"/",
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js"
).permitAll()
.antMatchers("/auth/**").permitAll()
.anyRequest().authenticated();
// Custom JWT based security filter
httpSecurity
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
// disable page caching
httpSecurity.headers().cacheControl();
}
}
I was under impression I can check against storeID field in WebAuthenticationDetailsSourceImpl, but looks like it has never been executed because I don't see anything related in log.
WebAuthenticationDetailsSourceImpl:
#Component
public class WebAuthenticationDetailsSourceImpl implements AuthenticationDetailsSource<HttpServletRequest, JwtAuthenticationRequest> {
#Override
public JwtAuthenticationRequest buildDetails(HttpServletRequest context) {
System.out.println("___#####_____");
System.out.println(context);
System.out.println("___#####_____");
return new JwtAuthenticationRequest();
}
}
cuz you don't insert "your" usernamePasswordAuthenticationFilter that set webAuthenticationDetailsSourceImpl to Spring Security's authentication filter chain.
perhaps current your authentication filter chain is
~
JwtAuthenticationTokenFilter
(Spring Security's original)UsernamePasswordAuthenticationFilter
~
hence,if you want to retrieve your additional parameter in "your" usernamePasswordAuthenticationFilter add this filter too like a JwtAuthenticationTokenFilter
but , if you want to simply retrieve parameter at JwtAuthenticationTokenFilter
use setAuthenticationDetailsSource at there
#Bean
public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
JwtAuthenticationTokenFilter authenticationTokenFilter = new JwtAuthenticationTokenFilter();
authenticationTokenFilter.setAuthenticationManager(authenticationManagerBean());
authenticationTokenFilter.setAuthenticationDetailsSource(webAuthenticationDetailsSourceImpl);
return authenticationTokenFilter;
}

Resources