Spring Boot JWT - Adding BCrypt Security - Getting Access Denied - spring-boot

I have an application server which uses Spring boot framework with JWT token. I want to encrypt the user password, but running into login issues. I am able to encrypt user's password using
userModel.setPassword(new BCryptPasswordEncoder().encode(userModel.getPassword()));
but when trying to login I am getting Encoded password does not look like BCrypt. I tried to change my authenticate method and encrypt the password from login but it didn't work.
new UsernamePasswordAuthenticationToken(
authenticationRequest.getUsername(),
new BCryptPasswordEncoder().encode( authenticationRequest.getPassword())));
I would appreciate your help, if you could point me to the direct direction or give me solution. below is my Security config file.
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final MyUserDetailService myUserDetailService;
private final JwtRequestFilter jwtRequestFilter;
public SecurityConfig(MyUserDetailService myUserDetailService, JwtRequestFilter jwtRequestFilter) {
this.myUserDetailService = myUserDetailService;
this.jwtRequestFilter = jwtRequestFilter;
}
#Override
// Authentication : User --> Roles
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.authenticationProvider(authProvider());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public DaoAuthenticationProvider authProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(myUserDetailService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
#Override
// Authorization : Role -> Access
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.formLogin().disable()
.headers().frameOptions().disable()
.and()
.authorizeRequests()
.antMatchers("/authenticate").permitAll() .and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
}
I am not able to authenticate(login) using the encrypted password or the raw password. Please let me know what I can do to fix.
Thank you for your help.

Hey i had the same problem like you, i am just from solving it.
new UsernamePasswordAuthenticationToken(
authenticationRequest.getUsername(),
authenticationRequest.getPassword())
);
replace that part of your code with mine.
By calling again new BCryptPasswordEncoder().encode you create another salt value different from the one previously created when saving the user in your Database.
And by login you will never get the same value as the one in the Database.

The exception is thrown if the stored password in the database is not encrypted correctly.
Make sure it starts with $2a$ or $2b$ or $2y$ and is exactly 60 chars long.

Related

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.

Spring Security basic auth for REST Api single login needed?

I am using Spring Security basic auth for my REST API.
Initially I get unauthorized HTTP response status for unauthenticated secured routes.
If I provide the right credentials, I get a Ok HTTP response status, but after a single successful login, I can access all the secured routes without providing user credentials.
Here are my questions:
Is it the correct behaviour for basic authentication?
Why it happens?
My security config:
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserService userService;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userService);
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
//J-
http.csrf().disable()
.authorizeRequests()
.antMatchers("/save")
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/h2-console/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.httpBasic();
//J+
//adding support for h2 console, otherwise crashes
http.headers().frameOptions().disable();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(authenticationProvider());
}
}
and here is the UserDetailsService's loadByUsername() method:
#Override
public UserDetails loadUserByUsername(String username) {
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UserNotFoundException(username);
} else if (UserStatus.Deactivated.equals(user.getStatus())) {
throw new UserDeactivatedException(username);
}
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), Collections.singleton(new SimpleGrantedAuthority("USER")));
}
https://www.baeldung.com/spring-security-session
Refer mentioned link. For Restful API’s use stateless session policy

CORS problems with Spring Security and Websocket

I am developing an Ionic app based on a Spring backend.
I implemented Spring Security with JWT authentication. My app will have a chat room where users can talk each other in private or public chat. So, I am implementing a WebSocket system in order to get all updates in real time.
This is my Security Configuration:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
#Autowired
private UserDetailsService userDetailsService;
private AuthenticationManager authenticationManager;
#Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.userDetailsService(this.userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
return new JwtAuthenticationTokenFilter();
}
// configurazione Cors per poter consumare le api restful con richieste ajax
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.addAllowedOrigin("*");
configuration.setAllowedMethods(Arrays.asList("POST, PUT, GET, OPTIONS, DELETE"));
configuration.addAllowedHeader("*");
configuration.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class)
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().cors().and()
.authorizeRequests()
.antMatchers(
HttpMethod.GET,
"/",
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/image/**").permitAll()
.antMatchers("/socket/**").permitAll()
.antMatchers("/public/**").permitAll().and()
.authorizeRequests().anyRequest().authenticated().and();
httpSecurity.headers().cacheControl();
}
#Bean
public AuthenticationManager customAuthenticationManager() throws Exception {
return authenticationManager();
}
}
This is my WebSocket configuration:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfiguration extends AbstractWebSocketMessageBrokerConfigurer{
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/socket")
.setAllowedOrigins("*")
.withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/chat")
.enableSimpleBroker("/subscribe");
}
}
In this condition, I am currently facing this error:
Access to XMLHttpRequest at
'http://localhost:8080/SpringApp/socket/info?t=1547732425329' from
origin 'http://localhost:8100' has been blocked by CORS policy: The
value of the 'Access-Control-Allow-Origin' header in the response must
not be the wildcard '*' when the request's credentials mode is
'include'. The credentials mode of requests initiated by the
XMLHttpRequest is controlled by the withCredentials attribute.
Each call is working (i am perfectly authorized with jwt) but the WebSocket can't work.
So, I tried to simply remove the .cors() in configure method in my security configuration class. This lead me to an opposite problem:
error in chrome
Indeed, now WebSocket works perfectly, instead each api call gives me 401.
What's the correct way to resolve this problem?
Thank you
Yeah, I got the same error when I was working in a related issue in one of my projects. The solution was that I had to set the allowed-origin header value to the URL of my application. The wildcard value (*) is not allowed if you send credentials.

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.

Logout with Rest Template in Spring Security Application

I'm writing a client for my application. Spring stack is Spring 4 and Spring Security 4 (main parts).
I try to logout from my application in the following way:
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<String> entity = new HttpEntity<>("_csrf=" + csrfToken,
httpHeaders);
restTemplate.postForEntity(appUrl + "/logout", entity, String.class);
A RestTemplate object is created in the following way (before login of course):
new RestTemplate(new HttpComponentsClientHttpRequestFactory())
But I get the following exception on the server:
org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported at
org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.handleNoMatch(RequestMappingInfoHandlerMapping.java:207) at
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.lookupHandlerMethod(AbstractHandlerMethodMapping.java:374) at
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:314) at
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:61) at
org.springframework.web.servlet.handler.AbstractHandlerMapping.getHandler(AbstractHandlerMapping.java:352)
I was getting the following exception when I tried to login in the app. The only
way I managed to do it is getting login page and getting CSRF token from there. I tried to get token from the server in the following way and return it to the client:
#RequestMapping(value = "/api/csrf", method = RequestMethod.GET)
public String csrf(HttpServletRequest httpServletRequest) {
return ((CsrfToken) httpServletRequest.getAttribute(CsrfToken.class.getName())).getToken();
}
But with this token I was getting the same exception all the time.
Now I want to implement logout in any way at least but notes related to proper login with RestTemplate is appreciated too. Thanks!
UPDATE: adding security config
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final DataSource dataSource;
private final UserDetailsService splittingRolesUserDetails;
private final AccessDeniedHandler accessDeniedHandler;
#Autowired
public SecurityConfig(DataSource dataSource, UserDetailsService splittingRolesUserDetails,
AccessDeniedHandler accessDeniedHandler) {
this.dataSource = dataSource;
this.splittingRolesUserDetails = splittingRolesUserDetails;
this.accessDeniedHandler = accessDeniedHandler;
}
// overrides role prefix in case .access() in httpSecurity configuration
// just because it is needed in the task. hasRole() won't work
// as there are used different voters in AffirmativeBased.
// link to the related issue on GitHub:
// https://github.com/spring-projects/spring-security/issues/3701
#Bean
GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults("");
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.authenticationProvider(authenticationProvider())
.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery("select user_name, password, true from user where username=?");
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(splittingRolesUserDetails);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
#Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.authorizeRequests()
.antMatchers("/login/**").permitAll()
.antMatchers("/api/csrf").permitAll()
.antMatchers("/api/ticket/event**").access("hasRole('" + Role.BOOKING_MANAGER.toString() + "')")
.anyRequest().access("hasRole('" + Role.REGISTERED_USER.toString() + "')")
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/event")
.permitAll()
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler)
.accessDeniedPage("/403")
.and()
.rememberMe()
.userDetailsService(splittingRolesUserDetails);
}
}
No need to send your token from an endpoint that is not secured, that contradicts the principle for which the token is used in the first place. You can store your token in a cookie with HTTP only access by adding this to your config:
.and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
You can then retrieve it from a cookie named XSRF-TOKEN.

Resources