I'm using Spring security and use an encoder to encode passwords.
So, in my Spring Security Config I have auto wired the
PasswordEncoder passwordEncoder() and added it to the DaoAuthenticationProvider, this is my Spring Security config
package it.besmart.easyparking.config;
import javax.sql.DataSource;
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.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor;
import org.springframework.web.servlet.support.RequestDataValueProcessor;
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("customUserDetailsService")
UserDetailsService userDetailsService;
#Autowired
CustomSuccessHandler customSuccessHandler;
#Autowired
CustomAuthenticationFailureHandler customAuthenticationFailureHandler;
#Autowired
DataSource dataSource;
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
auth.authenticationProvider(authenticationProvider());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/", "/home", "/user/**").permitAll()
.antMatchers("/spots/**", "/parks/**", "/floors/**", "/lights/**", "/sensors/**", "/illumination/**",
"/occupation/**", "/movement/**", "/map/**", "/include/**")
.access("hasRole('USER') or hasRole('ADMIN') or hasRole('PARK')").antMatchers("/admin/**")
.access("hasRole('ADMIN') and hasRole('PARK')").antMatchers("/updatePassword")
.hasAuthority("CHANGE_PASSWORD_PRIVILEGE").
and().formLogin().loginPage("/login").successHandler(customSuccessHandler)
.failureHandler(customAuthenticationFailureHandler).usernameParameter("email")
.passwordParameter("password").and().rememberMe().rememberMeParameter("remember-me")
.tokenRepository(persistentTokenRepository()).tokenValiditySeconds(86400).and().csrf().and()
.exceptionHandling().accessDeniedPage("/Access_Denied");
}
#Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl tokenRepositoryImpl = new JdbcTokenRepositoryImpl();
tokenRepositoryImpl.setDataSource(dataSource);
return tokenRepositoryImpl;
}
#Bean
public RequestDataValueProcessor requestDataValueProcessor() {
return new CsrfRequestDataValueProcessor();
}
}
When i pass data from my DTO to the Model I simply do the following
user.setPassword(passwordEncoder.encode(accountDTO.getPassword()));
And in my DB i see the encoded password, for instance something like $2a$10$vVCWjKltOiYO0nPYT1qYI.z4TSk2QJqViDOqRfmoB6BAgldF4vAmm
But when i try to login i'm getting
org.springframework.security.authentication.BadCredentialsException: Bad credentials
When i see logs, i find this
o.s.s.c.bcrypt.BCryptPasswordEncoder : Encoded password does not look like BCrypt
My password field in the DB is varchar(100) so i think there is enough space to store it...
If i change the encoded password in the DB with a decoded one, i can login...
this is my CustoUserDetailsService
#Service("customUserDetailsService")
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
UserRepository repository;
private final Logger logger = LoggerFactory.getLogger(CustomUserDetailsService.class);
#Override
#Transactional(readOnly = true)
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
try {
User user = repository.findByEmail(email);
if (user == null) {
throw new UsernameNotFoundException("No user found with username: " + email);
}
logger.debug("user: " + user.toString());
return new org.springframework.security.core.userdetails.User(user.getEmail(),
user.getPassword(), user.isEnabled(), accountNonExpired, credentialsNonExpired, accountNonLocked,
getAuthorities(user));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private List<GrantedAuthority> getAuthorities(User user) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority("ROLE_" + user.getUserProfile().getType()));
// System.out.print("authorities :"+authorities);
return authorities;
}
}
The exception has been thrown by UserDetailsService.loadUserByUsername() method at the return position because of the wrong instantiated User object that hasn't received the encoded password in the corresponding field.
The correct code would look like this:
...
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
UserEntity user = userRepository.findByUsername(username);
if (user == null)
throw new UsernameNotFoundException("Bad credentials");
return new User(
user.getUsername(),
user.getPassword(), // shall to be the already BCrypt-encrypted password
getAuthorities());
}
The org.springframework.security.authentication.BadCredentialsException: Bad credentials will be thrown once the user.getPassword() isn't well formed BCrypt hashsum.
The password encoder may be registered like this:
#Autowired
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(bCryptPasswordEncoder);
}
That's how it works.
The PasswordEncoder should be set like this:
#Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
Also you need to use the passwordEncoder for the users and clients.
You can check this repository https://github.com/dzinot/spring-boot-2-oauth2-authorization-jwt
It uses Spring Boot 2, JWT tokens and the Users and Clients are stored in the database.
Related
This question already has an answer here:
Custom security is not working when extending the WebSecurityConfigurerAdapter class in different package
(1 answer)
Closed 3 years ago.
I get a 401 unauthorized no matter what ever the request I try. I am even unable to get into #PostMapping("/signup") and print to the console. Spring doesn't show any errors. I only get 401 unauthorized when I try POST to signup with PostMan. Here, I am posting the code for WebSecurityConfig and REST Controller to signup with my REST API.
To access the whole code, please visit https://github.com/BhargaviNadendla/Discussion-Forum--Spring-Boot-Angular
WebSecurityConfig.java:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(
prePostEnabled = true
)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsServiceImpl userDetailsService;
#Autowired
private JwtAuthEntryPoint unauthorizedHandler;
#Bean
public JwtAuthTokenFilter authenticationJwtTokenFilter() {
return new JwtAuthTokenFilter();
}
#Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().
authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().formLogin().disable();
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
RestController:
#CrossOrigin(origins = "*", maxAge = 3600)
#RestController
#RequestMapping("/api/auth")
public class AuthRestAPIs {
#Autowired
AuthenticationManager authenticationManager;
#Autowired
UserDAO userRepository;
#Autowired
RoleDAO roleRepository;
#Autowired
PasswordEncoder encoder;
#Autowired
JwtProvider jwtProvider;
#PostMapping("/signin")
public ResponseEntity<?> authenticateUser(#Valid #RequestBody LoginForm loginRequest) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = jwtProvider.generateJwtToken(authentication);
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
return ResponseEntity.ok(new JwtResponse(jwt, userDetails.getUsername(), userDetails.getAuthorities()));
}
#PostMapping("/signup")
public ResponseEntity<?> registerUser(#Valid #RequestBody SignUpForm signUpRequest) {
System.out.println("In rest----------------------------------");
if (userRepository.existsByUsername(signUpRequest.getUsername())) {
return new ResponseEntity<>(new ResponseMessage("Fail -> Username is already taken!"),
HttpStatus.BAD_REQUEST);
}
if (userRepository.existsByEmail(signUpRequest.getEmail())) {
return new ResponseEntity<>(new ResponseMessage("Fail -> Email is already in use!"),
HttpStatus.BAD_REQUEST);
}
// Creating user's account
User user = new User(signUpRequest.getName(), signUpRequest.getUsername(), signUpRequest.getEmail(),
encoder.encode(signUpRequest.getPassword()));
Set<String> strRoles = signUpRequest.getRole();
Set<Role> roles = new HashSet<>();
strRoles.forEach(role -> {
switch (role) {
case "admin":
Role adminRole = roleRepository.findByName(RoleName.ROLE_ADMIN)
.orElseThrow(() -> new RuntimeException("Fail! -> Cause: User Role not find."));
roles.add(adminRole);
break;
default:
Role userRole = roleRepository.findByName(RoleName.ROLE_USER)
.orElseThrow(() -> new RuntimeException("Fail! -> Cause: User Role not find."));
roles.add(userRole);
}
});
user.setRoles(roles);
userRepository.save(user);
return new ResponseEntity<>(new ResponseMessage("User registered successfully!"), HttpStatus.OK);
}
}
AuthTokenFilter.java
package com.springboot.forumforall.jwtauth.security.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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;
import com.springboot.forumforall.jwtauth.security.services.UserDetailsServiceImpl;
public class JwtAuthTokenFilter extends OncePerRequestFilter {
#Autowired
private JwtProvider tokenProvider;
#Autowired
private UserDetailsServiceImpl userDetailsService;
private static final Logger logger = LoggerFactory.getLogger(JwtAuthTokenFilter.class);
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
String jwt = getJwt(request);
if (jwt != null && tokenProvider.validateJwtToken(jwt)) {
String username = tokenProvider.getUserNameFromJwtToken(jwt);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
logger.error("Can NOT set user authentication -> Message: {}", e);
}
filterChain.doFilter(request, response);
}
private String getJwt(HttpServletRequest request) {
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
return authHeader.replace("Bearer ", "");
}
return null;
}
}
Your problem is here:
#Override
protected void configure(HttpSecurity http) throws Exception {
http...
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
..
}
Which sould be:
#Override
protected void configure(HttpSecurity http) throws Exception {
http...
.anyRequest().authenticated()
.antMatchers("/api/auth/**").permitAll()
..
}
I am new to Spring Boot Security and try to implement JWT based authentication & authorisation. I have made all the required setup configuration for spring security. Even with right credentials, from Spring Security I am getting error - Bad Credential.
I have hard coded the user details which need to be supplied for spring security (No database call).
My code is like below,
SpringConfig.java
package com.wocs.rest.controller;
//import com.example.polls.security.CustomUserDetailsService;
//import com.example.polls.security.JwtAuthenticationEntryPoint;
//import com.example.polls.security.JwtAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.BeanIds;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import com.wocs.rest.security.CustomUserDetailsService;
import com.wocs.rest.security.JwtAuthenticationEntryPoint;
import com.wocs.rest.security.JwtAuthenticationFilter;
/**
* Created by rajeevkumarsingh on 01/08/17.
*/
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(
securedEnabled = true,
jsr250Enabled = true,
prePostEnabled = true
)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
CustomUserDetailsService customUserDetailsService;
#Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
#Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
#Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.userDetailsService(customUserDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean(BeanIds.AUTHENTICATION_MANAGER)
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/",
"/favicon.ico",
"/**/*.png",
"/**/*.gif",
"/**/*.svg",
"/**/*.jpg",
"/**/*.html",
"/**/*.css",
"/**/*.js")
.permitAll()
.antMatchers("/auth/login")
.permitAll()
.anyRequest()
.authenticated();
// Add our custom JWT security filter
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
CustomUserDetailsService.java
package com.wocs.rest.security;
import com.wocs.services.user.iface.UserServiceIface;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* Created by rajeevkumarsingh on 02/08/17.
*/
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UserServiceIface userService;
public void setUserService(UserServiceIface userService) {
this.userService = userService;
}
#Override
#Transactional
public UserDetails loadUserByUsername(String loginIDorPhoneOrEmail)
throws UsernameNotFoundException {
// Let people login with either loginID or email or Phone
//User user = new User();
//UserPassword userPassword = new UserPassword();
List<String> permissions = new ArrayList<>();
permissions.add("P1");
permissions.add("P2");
permissions.add("P3");
UserDetails userDetails = UserPrincipal.create(115, loginIDorPhoneOrEmail,"222",true, permissions);
return userDetails;
}
}
JWTAuthenticationEntryPoint.java
package com.wocs.rest.security;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Created by rajeevkumarsingh on 07/12/17.
*/
#Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationEntryPoint.class);
#Override
public void commence(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
logger.error("Responding with unauthorized error. Message - {}", e.getMessage());
httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED,
"Sorry, You're not authorized to access this resource.");
}
}
UserPrincipal.java
package com.wocs.rest.security;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public class UserPrincipal implements UserDetails {
private int id;
private String username;
#JsonIgnore
private String password;
private boolean isCredentialNonExpired = false;
private Collection<? extends GrantedAuthority> authorities;
public UserPrincipal(int id, String username, String password, boolean isCredentialNonExpired, Collection<? extends GrantedAuthority> authorities) {
this.id = id;
this.username = username;
this.password = password;
this.authorities = authorities;
this.isCredentialNonExpired = isCredentialNonExpired;
}
public static UserPrincipal create(int userID, String userName, String password,boolean isCredentialNonExpired, List<String> permissions) {
List<GrantedAuthority> authorities = permissions.stream().map(permission ->
new SimpleGrantedAuthority(permission)
).collect(Collectors.toList());
return new UserPrincipal(
userID,
userName,
password,
isCredentialNonExpired,
authorities
);
}
public int getId() {
return id;
}
#Override
public String getUsername() {
return username;
}
#Override
public String getPassword() {
return password;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return isCredentialNonExpired;
}
#Override
public boolean isEnabled() {
return true;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UserPrincipal that = (UserPrincipal) o;
return Objects.equals(id, that.id);
}
#Override
public int hashCode() {
return Objects.hash(id);
}
}
AuthController.java
#RequestMapping(value = "/login", method = RequestMethod.POST, consumes = "text/plain")
public Map login(#RequestBody String stringRequestBody) throws JsonParseException, JsonMappingException, IOException, ServiceException
{
logger.info("Auth JSON Request:" + stringRequestBody);
Map<String, String> jsonMap = parseJSON(stringRequestBody);
String username = "rahul#gmail.com";
String password = "222";
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(username,password)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = tokenProvider.generateToken(authentication);
User user = userService.getUserByLoginIDOrPhoneOrEmail(username);
Map userDetail = new HashMap<String, Object>();
if (user != null) {
userDetail.put("user", user);
userDetail.put("userType", userService.getUserTypeByUserID(user.getID()));
userDetail.put("permissions", userService.getUserPermissions(user.getID()));
userDetail.put("token", jwt);
}
return userDetail;
}
I am trying to hit the api, but its throwing me:
{
"timestamp":"2018-08-08T08:42:14.575+0000",
"status":401,
"error":"Unauthorized",
"message":"Sorry, You're not authorized to access this resource.",
"path":"/auth/login"
}
Log error - Responding with unauthorized error. Message - Bad credentials
I had referred below link for spring security implementation
https://www.callicoder.com/spring-boot-spring-security-jwt-mysql-react-app-part-2/
Please Help me.
I want to enable admin to access admin page and do admin stuff, but when I try to do that by setting that the url with /admin/** can only be accessed by user with role admin, it returns 403 Forbidden, access denied. But the user has authorities set to ROLE_ADMIN I checked. What am I doing wrong?
My Controller for user login
#RestController
public class UserController {
#Autowired
AuthenticationManager authenticationManager;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private AuthorityService authorityService;
#Autowired
private UserAuthorityService userAuthorityService;
#Autowired
TokenUtils tokenUtils;
#Autowired
private UserService userService;
#RequestMapping(value = "/api/login", method = RequestMethod.POST, produces = "text/html")
public ResponseEntity<String> login(#RequestBody LoginDTO loginDTO) {
try {
// System.out.println(loginDTO.getUsername() + " " + loginDTO.getPassword());
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
loginDTO.getUsername(), loginDTO.getPassword());
Authentication authentication = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetails details = userDetailsService.loadUserByUsername(loginDTO.getUsername());
return new ResponseEntity<String>(tokenUtils.generateToken(details), HttpStatus.OK);
} catch (Exception ex) {
return new ResponseEntity<String>("Invalid login", HttpStatus.BAD_REQUEST);
}
}
#RequestMapping(value = "/api/register", method = RequestMethod.POST, produces = "text/html")
public ResponseEntity<String> register(#RequestBody RegisterDTO registerDTO) {
try {
System.out.println(registerDTO);
User user = userService.findUserByUsername(registerDTO.getUsername());
// // Check if user with that username exists
if(user != null){
// User with that username is found
return new ResponseEntity<String>("User with that username exists", HttpStatus.BAD_REQUEST);
}
// We need to save the user so his ID is generated
User newUser = userService.saveUser(new User(registerDTO));
UserAuthority userAuthority = userAuthorityService.save(new UserAuthority(newUser, authorityService.findOneByName("User")));
Set<UserAuthority> authorities = new HashSet<>();
authorities.add(userAuthority);
newUser.setUserAuthorities(authorities);
User savedUser = userService.save(newUser);
return new ResponseEntity<String>("You have registered successfully with username " + savedUser.getUsername(), HttpStatus.OK);
} catch (Exception ex) {
return new ResponseEntity<String>("Invalid register", HttpStatus.BAD_REQUEST);
}
}
}
I can say that I test my app with postman and login and registration are working fine. When the user is logged in I can the token with the correct data and users authorities, but why when I try to access /admin/building/add url it is returning 403 error?
My Controller for adding building for admin page:
#RestController
public class BuildingController {
#Autowired
private BuildingService buildingService;
#RequestMapping(value = "/admin/building/add", method = RequestMethod.POST, produces = "text/html")
public ResponseEntity<String> addBuilding(#RequestBody BuildingDTO buildingDTO) {
try{
Building newBuilding = new Building(buildingDTO);
return new ResponseEntity<String>(newBuilding.getName(), HttpStatus.OK);
}catch (Exception ex) {
return new ResponseEntity<String>("Data was not valid", HttpStatus.BAD_REQUEST);
}
}
}
My SecurityConfiguration.java
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
public void configureAuthentication(
AuthenticationManagerBuilder authenticationManagerBuilder)
throws Exception {
authenticationManagerBuilder
.userDetailsService(this.userDetailsService).passwordEncoder(
passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public AuthenticationTokenFilter authenticationTokenFilterBean()
throws Exception {
AuthenticationTokenFilter authenticationTokenFilter = new AuthenticationTokenFilter();
authenticationTokenFilter
.setAuthenticationManager(authenticationManagerBean());
return authenticationTokenFilter;
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/index.html", "/view/**", "/app/**", "/", "/api/login", "/api/register").permitAll()
// defined Admin only API area
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest()
.authenticated()
.and().csrf().disable();
//if we use AngularJS on client side
// .and().csrf().csrfTokenRepository(csrfTokenRepository());
//add filter for adding CSRF token in the request
httpSecurity.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class);
// Custom JWT based authentication
httpSecurity.addFilterBefore(authenticationTokenFilterBean(),
UsernamePasswordAuthenticationFilter.class);
}
/**
* If we use AngularJS as a client application, it will send CSRF token using
* name X-XSRF token. We have to tell Spring to expect this name instead of
* X-CSRF-TOKEN (which is default one)
* #return
*/
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}
I should mention that I am using Angularjs for frontend, but even so I can login and the correct authorities are displayed for that user. But for some reason I can not access the admin page, even if I login as admin.
Also I tried .hasAuthority("ROLE_ADMIN") and .hasRole("ROLE_ADMIN")(which displays an error for ROLE_) and so I changed it to .hasRole("ADMIN") but it is still not working.
In the database the role for admin is saved as ROLE_ADMIN.
Try like this :
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private static String REALM="MY_TEST_REALM";
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("bill").password("abc123").roles("ADMIN");
auth.inMemoryAuthentication().withUser("tom").password("abc123").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/user/**").hasRole("ADMIN")
.and().httpBasic().realmName(REALM).authenticationEntryPoint(getBasicAuthEntryPoint())
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);//We don't need sessions to be created.
}
#Bean
public CustomBasicAuthenticationEntryPoint getBasicAuthEntryPoint(){
return new CustomBasicAuthenticationEntryPoint();
}
/* To allow Pre-flight [OPTIONS] request from browser */
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
}
}
For a complet configuration example : Secure Spring REST API using Basic Authentication
import org.springframework.context.annotation.Configuration;
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 SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();// We don't need sessions to be created.
}
}
This did it for me. Now I am able to submit my post requests successfully
Try this in SecurityConfig:
.antMatchers("/api/admin").access("hasRole('ADMIN')")
.antMatchers("/api/user").access("hasRole('ADMIN') or hasRole('USER')")
I am working Spring-Boot, Spring Security with basic Authentication. I will send login url from my client application written in AngularJS via RESTful API call.
Everything works as expected. All the users in the DB configured in the SecurityConfiguration.java as below.
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
List<User> users = userService.getUsers();
for (User user : users) {
auth.inMemoryAuthentication().withUser(user.getUserName()).password(user.getPassword())
.roles(user.getRole().getName());
}
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().antMatchers("/server/rest/secure/**")
.hasRole("ADMIN").and()
.httpBasic().realmName(REALM).authenticationEntryPoint(getBasicAuthEntryPoint());
}
#Bean
public CustomBasicAuthenticationEntryPoint getBasicAuthEntryPoint() {
return new CustomBasicAuthenticationEntryPoint();
}
CustomBasicAuthenticationEntryPoint.java
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
public class CustomBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {
#Override
public void commence(final HttpServletRequest request,
final HttpServletResponse response,
final AuthenticationException authException) throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.addHeader("WWW-Authenticate", "Basic realm=" + getRealmName() + "");
PrintWriter writer = response.getWriter();
writer.println("HTTP Status 401 : " + authException.getMessage());
response.setHeader("WWW-Authenticate", "FormBased");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
#Override
public void afterPropertiesSet() throws Exception {
setRealmName("MY_TEST_REALM");
super.afterPropertiesSet();
}
}
So If I signup a new user which will inserted in the DB but not added in the above implementation. So authentication fails.
How can refresh the above implementation whenever i'm and doing signup of a new user
When doing authentication with db, you should do the following:
#Service("userDetailsService")
#Transactional
public class MUserDetailService implements UserDetailsService {
#Autowired
AppUserDao appUserDao;
#Override
public UserDetails loadUserByUsername(final String appUserName) throws UsernameNotFoundException {
AppUser appUser = appUserDao.findByName(appUserName);
if (appUser == null) throw new UsernameNotFoundException(appUserName);
else{
return new User(appUser.getUsername(),appUser.getPassword(),appUser.getActive(),true,true,true,getGrantedAuthorities(appUser));
}
}
private List<GrantedAuthority> getGrantedAuthorities(AppUser appUser){
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
for (Authority authority : appUser.getAuthorities()){
authorities.add(new SimpleGrantedAuthority(authority.getAuthorityName()));
}
return authorities;
}
}
and then define SecurityConfiguration as follows:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
#Autowired
#Qualifier("userDetailsService")
UserDetailsService userDetailsService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
}
I have this listener in the context of Spring:
package listeners;
import java.util.Date;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.security.authentication.event.AbstractAuthenticationEvent;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
import org.springframework.stereotype.Component;
import services.UserService;
import services.security.CustomUserDetails;
/**
*
* #author sergio
*/
#Component
public class AuthenticationSuccessEventHandler{
private static Logger logger = LoggerFactory.getLogger(AuthenticationSuccessEventHandler.class);
#Autowired
private UserService userService;
#EventListener({AuthenticationSuccessEvent.class, InteractiveAuthenticationSuccessEvent.class})
public void processAuthenticationSuccessEvent(AbstractAuthenticationEvent e) {
logger.info("Autenticación realizada ....");
// Actualizamos la útltima fecha de acceso
String username = ((CustomUserDetails) e.getAuthentication().getPrincipal()).getUsername();
logger.info("Actualizando último acceso para user: " + username);
userService.updateLastLoginAccess(username, new Date());
}
}
This is successfully created in context, according to the Spring debug messages.
DEBUG DefaultListableBeanFactory:448 - Creating instance of bean 'authenticationSuccessEventHandler'
2016-12-11 11:33:29 DEBUG InjectionMetadata:72 - Registered injected element on class [listeners.AuthenticationSuccessEventHandler]: AutowiredFieldElement for private services.UserService listeners.AuthenticationSuccessEventHandler.userService
When I authenticate correctly in the application, no event is released by Spring Security and therefore this Event Listener is not called.
My Spring Security configuration is this
#Configuration
#EnableWebSecurity
#ComponentScan(basePackageClasses = CustomUserDetailsService.class)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Bean
public PasswordEncoder passwordEncoder() {
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/signup").anonymous()
.antMatchers("/admin/**").authenticated()
.anyRequest().permitAll()
.and()
.formLogin().loginPage("/admin/login").permitAll()
.usernameParameter("username").passwordParameter("password")
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/admin/logout"))
.logoutSuccessUrl("/admin/login?logout")
.and()
.exceptionHandling().accessDeniedPage("/403")
.and()
.csrf();
}
}
SecurityWebApplicationInitializer
package config;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
/**
*
* #author sergio
*/
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
}
I am using Spring Security 4.2.0.RELEASE.
You may need to register the event-publishing infrastructure (eg. by configuring a DefaultAuthenticationEventPublisher).
#EnableWebSecurity
class SecurityConfig extends WebSecurityConfigurerAdapter {
...
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationEventPublisher(authenticationEventPublisher())
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean
public DefaultAuthenticationEventPublisher authenticationEventPublisher() {
return new DefaultAuthenticationEventPublisher();
}
}
This is how i achieved it.
1) In your Application class, expose your application listener like
#Bean
public ApplicationListener applicationListener(){
return new AuthSuccessApplicationListener();
}
2) Implement AuthSuccessApplicationListener for example
public class AuthSuccessApplicationListener implements
ApplicationListener<InteractiveAuthenticationSuccessEvent>{
#Autowired(required=false)
HttpSession httpSession;
#Autowired
Environment env;
/**
* Handle an application event.
*
* #param appEvent the event to respond to
*/
#Override
public void onApplicationEvent(InteractiveAuthenticationSuccessEvent appEvent) {
if (appEvent!=null) {
LdapUserDetailsImpl ldapUserDetailsImpl = (LdapUserDetailsImpl) appEvent.getAuthentication().getPrincipal();
try {
if (ldapUserDetailsImpl != null) {
logger.info("Session Created for " + ldapUserDetailsImpl.getUsername());
if (httpSession.getAttribute("adminUser") == null) {
// check user is admin and set into session
if (isAdminUser(ldapUserDetailsImpl.getUsername())) {
httpSession.setAttribute("adminUser", "ADMIN_USER");
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(auth.getAuthorities());
// Add the ROLE_ADMIN into Authorities
authorities.add(new SimpleGrantedAuthority(SecurityConfig.ADMIN));
// Create a new Authentication based on current principal and authorities and set into Security Context
Authentication newAuth = new UsernamePasswordAuthenticationToken(auth.getPrincipal(), auth.getCredentials(), authorities);
SecurityContextHolder.getContext().setAuthentication(newAuth);
}
}
}
} catch (Exception e) {
logger.error("Exception occurred : " + e.getMessage());
}
}
}
Here's how the Spring Security docs explain it (at the time of writing, Spring Security is at version 5.6.1):
To listen for these events, you must first publish an AuthenticationEventPublisher. Spring Security’s DefaultAuthenticationEventPublisher will probably do fine:
#Bean
public AuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher appEventPublisher) {
return new DefaultAuthenticationEventPublisher(appEventPublisher);
}
See https://docs.spring.io/spring-security/reference/servlet/authentication/events.html
In spring-security version 5.6.0 only UsernamePasswordAuthenticationFilter fires InteractiveAuthenticationSuccessEvent. As option You may extends AbstractAuthenticationProcessingFilter or do it by own a success handler implementation. Example:
class LoggingAuthenticationSuccessHandler extends WebFilterChainServerAuthenticationSuccessHandler {
#Autowired
private AuthenticationEventPublisher eventPublisher;
#Override
public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {
this.eventPublisher.publishAuthenticationSuccess(authentication);
return super.onAuthenticationSuccess(webFilterExchange,authentication);
}
}