DataBase authentication without WebSecurityConfigurerAdapter - java

I am trying to implement a database authentication using custom UserDetails implementation. I have three roles, which are STUDENT, ADMIN and ADMINTRAINEE (these are enums) and some authorities provided for them , which I fetch from in-memory db (but I'm gonna switch to external one). This is web security configuration:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class ApplicationSecurityConfig {
private final PasswordEncoder passwordEncoder;
private final ApplicationUserService userService;
#Autowired
public ApplicationSecurityConfig(PasswordEncoder passwordEncoder,ApplicationUserService userService) {
this.passwordEncoder = passwordEncoder;
this.userService = userService;
}
#Bean
protected SecurityFilterChain filterChain(HttpSecurity http)
throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/", "index", "/css/*", "/js/*").permitAll()
.antMatchers("/api/**").hasRole(STUDENT.name())
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.defaultSuccessUrl("/courses", true)
.passwordParameter("password")
.usernameParameter("username")
.and()
.rememberMe()
.tokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(21))
.key("example")
.rememberMeParameter("remember-me")
.and()
.logout()
.logoutUrl("/logout")
.logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET"))
.clearAuthentication(true)
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID", "remember-me")
.logoutSuccessUrl("/login"); // custom address to redirect after logout
return http.build();
}
// This is what I need to rewrite
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(daoAuthenticationProvider());
}
// Is used to utilize a custom impl of UserDetailsService
#Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder);
provider.setUserDetailsService(userService);
return provider;
}
}
This is what the implementation of the UserDetailsService looks like:
#Service
public class ApplicationUserService implements UserDetailsService {
private final ApplicationUserDao applicationUserDao;
#Autowired
public ApplicationUserService(#Qualifier("fake") ApplicationUserDao applicationUserDao) {
this.applicationUserDao = applicationUserDao;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return applicationUserDao
.selectApplicationUserByUsername(username)
.orElseThrow(() ->
new UsernameNotFoundException(String.format("Username %s not found", username)));
}
}
So it calls the selectApplicationUserByUsername() method which is here:
public interface ApplicationUserDao {
Optional<ApplicationUser> selectApplicationUserByUsername(String username);
}
And this is the implementation of that interface:
#Repository("fake")
public class FakeApplicationUserDaoService implements
ApplicationUserDao {
private final PasswordEncoder passwordEncoder;
#Autowired
public FakeApplicationUserDaoService(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
#Override
public Optional<ApplicationUser> selectApplicationUserByUsername(String username) {
return getApplicationUsers().stream()
.filter(applicationUser -> username.equals(applicationUser.getUsername()))
.findFirst();
}
private List<ApplicationUser> getApplicationUsers() {
List<ApplicationUser> applicationUsers = Lists.newArrayList(
new ApplicationUser(
"annasmith",
passwordEncoder.encode("password"),
STUDENT.getGrantedAuthorities(),
true,
true,
true,
true
),
new ApplicationUser(
"linda",
passwordEncoder.encode("password"),
ADMIN.getGrantedAuthorities(),
true,
true,
true,
true
),
new ApplicationUser(
"tom",
passwordEncoder.encode("password"),
ADMINTRAINEE.getGrantedAuthorities(),
true,
true,
true,
true
)
);
return applicationUsers;
}
}
And this is the ApplicationUser class which is a custom subsitute to the UserDetails default implementation class Spring Security uses:
public class ApplicationUser implements UserDetails {
private final Set<? extends GrantedAuthority> grantedAuthorities;
private final String password;
private final String username;
private final boolean isAccountNonExpired;
private final boolean isAccountNonLocked;
private final boolean isCredentialsNonExpired;
private final boolean isEnabled;
public ApplicationUser(String password,
String username,
Set<? extends GrantedAuthority> grantedAuthorities,
boolean isAccountNonExpired,
boolean isAccountNonLocked,
boolean isCredentialsNonExpired,
boolean isEnabled) {
this.grantedAuthorities = grantedAuthorities;
this.password = password;
this.username = username;
this.isAccountNonExpired = isAccountNonExpired;
this.isAccountNonLocked = isAccountNonLocked;
this.isCredentialsNonExpired = isCredentialsNonExpired;
this.isEnabled = isEnabled;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return grantedAuthorities;
}
#Override
public String getPassword() {
return password;
}
#Override
public String getUsername() {
return username;
}
#Override
public boolean isAccountNonExpired() {
return isAccountNonExpired;
}
#Override
public boolean isAccountNonLocked() {
return isAccountNonLocked;
}
#Override
public boolean isCredentialsNonExpired() {
return isCredentialsNonExpired;
}
#Override
public boolean isEnabled() {
return isEnabled;
}
}
So these are the enums for roles and authorities I wrote (the purpose of these is only for creating roles and the authorities users have; STUDENT does not have any authorities):
public enum ApplicationUserRole {
STUDENT(Sets.newHashSet()), // Sets is a class from the external library Guava
ADMIN(Sets.newHashSet(COURSE_READ, COURSE_WRITE, STUDENT_READ, STUDENT_WRITE)),
ADMINTRAINEE(Sets.newHashSet(COURSE_READ, STUDENT_READ));
private final Set<ApplicationUserPermission> permissions;
ApplicationUserRole(Set<ApplicationUserPermission> permissions) {
this.permissions = permissions;
}
public Set<ApplicationUserPermission> getPermissions() {
return permissions;
}
public Set<SimpleGrantedAuthority> getGrantedAuthorities() {
Set<SimpleGrantedAuthority> permissions = getPermissions().stream()
.map(permission -> new SimpleGrantedAuthority(permission.getPermission()))
.collect(Collectors.toSet());
permissions.add(new SimpleGrantedAuthority("ROLE_" + this.name()));
return permissions;
}
}
This is the ApplicationUserPermission class:
public enum ApplicationUserPermission {
STUDENT_READ("student:read"),
STUDENT_WRITE("student:write"),
COURSE_READ("course:read"),
COURSE_WRITE("course:write");
private final String permission;
ApplicationUserPermission(String permission) {
this.permission = permission;
}
public String getPermission() {
return permission;
}
}
And PasswordConfig class:
#Configuration
public class PasswordConfig {
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(10);
}
}
The granted authorities then are used in a controller class for ADMIN and ADMINTRAINEE with the #PreAuthorize annotation.
So the problem I have is in ApplicationSecurityConfig. I don't know how to call the AuthenticationManangerBuilder to pass that daoAuthenticationProvider I have. In older versions of Spring Security, I could just override the configure method with AuthenticationManagerBuilder instance as an argument, but It's no longer the case since that abstract class is currently deprecated. So how do I rewrite the method? Or do I even have to do so? Pls any help is appreciated.

My mistake was about the order of credentials variables in ApplicationUser constructor.As it turned out it is very important.
What was the mistake:
public ApplicationUser(String password,
String username,
Set<? extends GrantedAuthority> grantedAuthorities,
boolean isAccountNonExpired,
boolean isAccountNonLocked,
boolean isCredentialsNonExpired,
boolean isEnabled) {
this.grantedAuthorities = grantedAuthorities;
this.password = password;
this.username = username;
this.isAccountNonExpired = isAccountNonExpired;
this.isAccountNonLocked = isAccountNonLocked;
this.isCredentialsNonExpired = isCredentialsNonExpired;
this.isEnabled = isEnabled;
}
How it should be written:
public ApplicationUser(String username,
String password,
Set<? extends GrantedAuthority> grantedAuthorities,
boolean isAccountNonExpired,
boolean isAccountNonLocked,
boolean isCredentialsNonExpired,
boolean isEnabled) {
this.username = username;
this.password = password;
this.grantedAuthorities = grantedAuthorities;
this.isAccountNonExpired = isAccountNonExpired;
this.isAccountNonLocked = isAccountNonLocked;
this.isCredentialsNonExpired = isCredentialsNonExpired;
this.isEnabled = isEnabled;
}
If you are using Spring Boot version with WebSecutiryConfigurerAdapter being deprecated, that configure method is not needed. You just build a securityFilterChain with the first method, and then provide password encoder and set userDetailsService for your db authentication.

Related

Why spring security context doesn`t persist authenticated custom user?

I am making small web-store on Java for university project using Spring Boot and WebLogic server. I don`t use Hibernate, only JDBC.
In controller I need to get my current Spring Security User. I made CustomUser that implements UserDetails class and UserDetailsService implementation.
When I login into the server it accepts my credentials, page reloads, but immediately forwards me back to login page. It should redirect me just to /rootPath/ instead.
Here is my Security Config class
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsServiceImpl userDetailsService;
#Bean
public PasswordEncoder getEncoder(){
return NoOpPasswordEncoder.getInstance();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/registration").not().fullyAuthenticated()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/", "/store/**").hasAnyRole("ADMIN", "USER")
.anyRequest().authenticated()
.and()
.formLogin()
.permitAll()
.and()
.logout()
.permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
super.configure(auth);
}
}
Custom User implementation
public class CustomUser implements UserDetails {
private int id = -1;
private String name;
private String surname;
private String phone;
private String email;
private String password;
private String role;
private ProductCart productCart;
public CustomUser(int id, String name, String surname, String phone, String email, String password, String role) {
this.id = id;
this.name = name;
this.surname = surname;
this.phone = phone;
this.email = email;
this.password = password;
this.role = role;
}
public String getEmail() {
return email;
}
#Override
public Collection<GrantedAuthority> getAuthorities() {
GrantedAuthority authority = new SimpleGrantedAuthority(role);
return Collections.singletonList(authority);
}
#Override
public String getPassword() {
return password;
}
#Override
public String getUsername() {
return email;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
public String getRole() {
return role;
}
}
I tried to permit all requests to my /rootPath/ and get my User directly from Security Context and it showed me anonymousUser, when I login and was redirected to /rootPath/. I think that for some reason Spring or forgets my credentials, or just don't use my custom user as SecurityContext user and still use standart Spring user (for which I don't provide any service). I know for sure that my user loads from database correctly.
Here is my CustomUser class and UserDetailsService implementation (I removed getters and setters)
CustomUser.java
public class CustomUser implements UserDetails {
private int id = -1;
private String name;
private String surname;
private String phone;
private String email;
private String password;
private String role;
private ProductCart productCart;
public CustomUser(int id, String name, String surname, String phone, String email, String password, String role) {
this.id = id;
this.name = name;
this.surname = surname;
this.phone = phone;
this.email = email;
this.password = password;
this.role = role;
productCart = new ProductCart();
}
#Override
public Collection<GrantedAuthority> getAuthorities() {
GrantedAuthority authority = new SimpleGrantedAuthority(role);
return Collections.singletonList(authority);
}
#Override
public String getPassword() {
return password;
}
#Override
public String getUsername() {
return email;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
}
UserDetailsServiceImpl.java
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
UserDao userDao;
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
CustomUser customUser = userDao.getByEmail(email);
if (customUser == null) {
throw new UsernameNotFoundException("No user found with email: " + email);
}
return customUser;
}
}
Maybe I forgot to configure something or just my realization is wrong in some way?
You are auto-wiring the PasswordEncoder but where is the bean ? Spring is expecting a bean for auto-wiring of type PasswordEncoder
Specify the bean in your configuration file - SecurityConfig :
#Bean
public PasswordEncoder getEncoder(){
return NoOpPasswordEncoder.getInstance();
}
You have to populate the SecurityContextHolder with the principal information you have fetched. For instance, you can extend OncePerRequestFilter and add the following:
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
final SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);

there is no passwordencoder mapped for the id null / how to map an id?

This is my first time using spring, spring security, or doing anything with jwts, so please be patient with me.
If I send a POST request to api/user/login using insomnia with the body
{"email": "validmail.com",
"password": "123"}
I get the error 500, Internal Server error, and the exception
ERROR 12736 --- [nio-8088-exec-8] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
I looked online and i understood it that adding for example {noop} to the front of the password would provide the id with which the password is to be encoded.
But changing the input to
{"email": "validmail.com",
"password": "{noop}123"}
did not change the results.
Now my question is: How do I provide an id for the DelegatingPasswordEncoder to delegate to?
Note that i haven't hashed the passwords entered into my database yet.
Relevant code:
#Entity
public class User implements UserDetails {
private static final long serialVersionUID = -9099175240545719086L;
#Id
#Column(nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false)
private String email;
#Column
private String writtenSignaturePath;
#Column
private String name;
#Column
private String passwordHash;
#Column(columnDefinition = "BOOLEAN NOT NULL DEFAULT FALSE")
private boolean isAdmin;
#Column
private String twoFACode;
protected User() {
}
public User(final String email) {
this.email = email;
}
#JsonIgnore
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
if (twoFACode != null){
String[] roles;
if (isAdmin){
roles = new String[]{"user", "admin"};
} else {
roles = new String[]{"user"};
}
return AuthorityUtils.createAuthorityList(roles);
}
return AuthorityUtils.createAuthorityList();
}
public Long getId() {
return id;
}
public String getEmail() {
return email;
}
public void setEmail(final String email) {
this.email = email;
}
public String getWrittenSignaturePath() {
return writtenSignaturePath;
}
public void setWrittenSignaturePath(final String writtenSignaturePath) {
this.writtenSignaturePath = writtenSignaturePath;
}
// This is called getUsername to satisfy the interface. Users are identified by their email, so this should work fine
#Override
public String getUsername() {
return email;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
// This is called getPassword to satisfy the interface.
#Override
public String getPassword() {
return passwordHash;
}
public String getPasswordHash() {
return passwordHash;
}
public void setPasswordHash(final String passwordHash) {
this.passwordHash = passwordHash;
}
public boolean isAdmin() {
return isAdmin;
}
public void setAdmin(final boolean admin) {
isAdmin = admin;
}
public String getTwoFACode() {
return twoFACode;
}
public void setTwoFACode(final String twoFACode) {
this.twoFACode = twoFACode;
}
#JsonIgnore
#Override
public boolean isAccountNonExpired() { //<6>
return true;
}
#JsonIgnore
#Override
public boolean isAccountNonLocked() {
return true;
}
#JsonIgnore
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#JsonIgnore
#Override
public boolean isEnabled() {
return true;
}
}
public interface UserRepository extends CrudRepository<User, String> {
User findByEmail(String email);
}
public interface UserService extends UserDetailsService {
}
#Service
class UserServiceImpl implements UserService {
private final UserRepository userRepository;
#Autowired
public UserServiceImpl(final UserRepository userRepository) {
this.userRepository = userRepository;
}
//This is called loadUserByUsername to satisfy the interface. Users are usually identified by their email.
#Override
public UserDetails loadUserByUsername(final String email) throws UsernameNotFoundException {
try {
return userRepository.findByEmail(email);
} catch (UsernameNotFoundException e) {
throw e;
}
}
public UserDetails loadUserByEmail(final String email) throws UsernameNotFoundException {
try {
return userRepository.findByEmail(email);
} catch (UsernameNotFoundException e) {
throw e;
}
}
}
#SpringBootApplication
#EnableJpaRepositories
#EnableTransactionManagement
public class ExampleApplication {
public static void main(final String... args) {
SpringApplication.run(ExampleApplication.class, args);
}
}
#ConfigurationProperties("security")
public final class SecurityConstants {
private String authLoginUrl;
private String jwtSecret;
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
private final SecurityConstants securityConstants;
public JwtAuthenticationFilter(AuthenticationManager authenticationManager, final SecurityConstants securityConstants) {
this.authenticationManager = authenticationManager;
this.securityConstants = securityConstants;
setFilterProcessesUrl(this.securityConstants.getAuthLoginUrl());
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {
String email = request.getParameter("email");
String password = request.getParameter("password");
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(email, password);
return authenticationManager.authenticate(authenticationToken);
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain, Authentication authentication) {
UserDetails user = (UserDetails) authentication.getPrincipal();
List<String> roles = user.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList());
byte[] signingKey = securityConstants.getJwtSecret().getBytes();
String token = Jwts.builder()
.signWith(Keys.hmacShaKeyFor(signingKey), SignatureAlgorithm.HS512)
.setHeaderParam("typ", securityConstants.getTokenType())
.setIssuer(securityConstants.getTokenIssuer())
.setAudience(securityConstants.getTokenAudience())
.setSubject(user.getUsername())
.setExpiration(new Date(System.currentTimeMillis() + 86400000)) // + 1 Tag
.claim("rol", roles)
.compact();
response.addHeader(securityConstants.getTokenHeader(), securityConstants.getTokenPrefix() + token);
}
}
// JWT Token-Standardvalues
private String tokenHeader;
private String tokenPrefix;
private String tokenType;
private String tokenIssuer;
private String tokenAudience;
public String getAuthLoginUrl() {
return authLoginUrl;
}
public void setAuthLoginUrl(String authLoginUrl) {
this.authLoginUrl = authLoginUrl;
}
public String getJwtSecret() {
return jwtSecret;
}
public void setJwtSecret(String jwtSecret) {
this.jwtSecret = jwtSecret;
}
public String getTokenHeader() {
return tokenHeader;
}
public void setTokenHeader(String tokenHeader) {
this.tokenHeader = tokenHeader;
}
public String getTokenPrefix() {
return tokenPrefix;
}
public void setTokenPrefix(String tokenPrefix) {
this.tokenPrefix = tokenPrefix;
}
public String getTokenType() {
return tokenType;
}
public void setTokenType(String tokenType) {
this.tokenType = tokenType;
}
public String getTokenIssuer() {
return tokenIssuer;
}
public void setTokenIssuer(String tokenIssuer) {
this.tokenIssuer = tokenIssuer;
}
public String getTokenAudience() {
return tokenAudience;
}
public void setTokenAudience(String tokenAudience) {
this.tokenAudience = tokenAudience;
}
}
#Configuration
#EnableGlobalMethodSecurity(securedEnabled = true)
public class MethodSecurityConfig
extends GlobalMethodSecurityConfiguration {
}
public class JwtAuthorizationFilter extends BasicAuthenticationFilter {
private static final Logger LOG = LoggerFactory.getLogger(JwtAuthorizationFilter.class);
private final SecurityConstants securityConstants;
public JwtAuthorizationFilter(AuthenticationManager authenticationManager, final SecurityConstants securityConstants) {
super(authenticationManager);
this.securityConstants = securityConstants;
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws IOException, ServletException {
UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
if (authentication == null) {
filterChain.doFilter(request, response);
return;
}
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String token = request.getHeader(securityConstants.getTokenHeader());
if (token != null && !token.equals("") && token.startsWith(securityConstants.getTokenPrefix())) {
try {
byte[] signingKey = securityConstants.getJwtSecret().getBytes();
Jws<Claims> parsedToken = Jwts.parserBuilder()
.setSigningKey(signingKey).build()
.parseClaimsJws(token.replace(securityConstants.getTokenPrefix(), "").strip());
String username = parsedToken.getBody().getSubject();
List<SimpleGrantedAuthority> authorities = ((List<?>) parsedToken.getBody()
.get("rol")).stream()
.map(authority -> new SimpleGrantedAuthority((String) authority))
.collect(Collectors.toList());
if (username != null && !username.equals("")) {
return new UsernamePasswordAuthenticationToken(username, null, authorities);
}
} catch (ExpiredJwtException exception) {
LOG.warn("Request to parse expired JWT : {} failed : {}", token, exception.getMessage());
} catch (UnsupportedJwtException exception) {
LOG.warn("Request to parse unsupported JWT : {} failed : {}", token, exception.getMessage());
} catch (MalformedJwtException exception) {
LOG.warn("Request to parse invalid JWT : {} failed : {}", token, exception.getMessage());
} catch (SignatureException exception) {
LOG.warn("Request to parse JWT with invalid signature : {} failed : {}", token, exception.getMessage());
} catch (IllegalArgumentException exception) {
LOG.warn("Request to parse empty or null JWT : {} failed : {}", token, exception.getMessage());
}
}
return null;
}
}
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
private final SecurityConstants securityConstants;
public JwtAuthenticationFilter(AuthenticationManager authenticationManager, final SecurityConstants securityConstants) {
this.authenticationManager = authenticationManager;
this.securityConstants = securityConstants;
setFilterProcessesUrl(this.securityConstants.getAuthLoginUrl());
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {
String line;
StringBuilder builder = new StringBuilder();
//Why doesn't this get closed? Idk, have to take care of this later
try {
BufferedReader reader = request.getReader();
while ((line=reader.readLine()) != null){
builder.append(line);
}
} catch (IOException e) {
e.printStackTrace();
}
String body = builder.toString();
String[] params = body.split(",");
String email = params[0].substring(12, params[0].length()-1);
String password = params[1].substring(14, params[1].length()-2);
System.out.println(email);
System.out.println(password);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(email, password);
return authenticationManager.authenticate(authenticationToken);
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain, Authentication authentication) {
UserDetails user = (UserDetails) authentication.getPrincipal();
List<String> roles = user.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList());
byte[] signingKey = securityConstants.getJwtSecret().getBytes();
String token = Jwts.builder()
.signWith(Keys.hmacShaKeyFor(signingKey), SignatureAlgorithm.HS512)
.setHeaderParam("typ", securityConstants.getTokenType())
.setIssuer(securityConstants.getTokenIssuer())
.setAudience(securityConstants.getTokenAudience())
.setSubject(user.getUsername())
.setExpiration(new Date(System.currentTimeMillis() + 86400000)) // + 1 Tag
.claim("rol", roles)
.compact();
response.addHeader(securityConstants.getTokenHeader(), securityConstants.getTokenPrefix() + token);
}
}
#Configuration
#EnableWebSecurity
#EnableAutoConfiguration
#EnableConfigurationProperties(SecurityConstants.class)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final SecurityConstants securityConstants;
#Autowired
public SecurityConfig(SecurityConstants securityConstants) {
this.securityConstants = securityConstants;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.authorizeRequests()
.antMatchers("/api/**").permitAll()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager(), securityConstants))
.addFilter(new JwtAuthorizationFilter(authenticationManager(), securityConstants))
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Autowired
public void configureGlobal(final UserDetailsService userDetailsService,
final PasswordEncoder passwordEncoder,
final AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
#Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration().applyPermitDefaultValues();
corsConfiguration.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "PATCH", "DELETE"));
source.registerCorsConfiguration("/**", corsConfiguration);
return source;
}
}
Spring Security requires a PasswordEncoder when working with user passwords, you have not provided information about it. You need declare bean with it, for example using BCryptPasswordEncoder:
#Bean
PasswordEncoder getPasswordEncoder() {
return new BCryptPasswordEncoder();
}
and use it in your code. You can find many examples on the internet, like this.
If, for any reason, we don't want to encode the configured password, we can make use of the NoOpPasswordEncoder.

403 forbidden instead of successful authorization! Why?

I am writing a Spring-MVC application using Spring Boot and Hibernate. The application has a User who has the role of Admin and Customer. I decided to add Spring Security JWT and did everything right. Now that I have everything ready, I want to get a TOKEN, I log in successfully, but in return I get 403 Forbidden. What have I done wrong? Did you configure configs incorrectly or did the roles incorrectly?
Configs:
#RequiredArgsConstructor
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// Fields
//
private final JwtTokenProvider jwtTokenProvider;
//
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().disable().csrf().disable()
.httpBasic().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/auth/login").permitAll()
.anyRequest().authenticated()
.and()
.apply(new JwtConfigurer(jwtTokenProvider));
}
}
Controller:
#RequiredArgsConstructor
#RestController
#RequestMapping("/auth")
public class AuthenticationController {
// Fields
//
private final AuthenticationManager authenticationManager;
private final JwtTokenProvider jwtTokenProvider;
private final UserService userService;
//
// GET-Methods
//
//
#PostMapping("/login")
public ResponseEntity<Map<String, String>> login(#RequestBody AuthenticationRequestDTO requestDto) {
try {
String login = requestDto.getLogin();
authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(login, requestDto.getPassword()));
User user = userService.findByLogin(login);
String token = jwtTokenProvider.createToken(login, user.getRole());
Map<String, String> response = new HashMap<>();
response.put("login", login);
response.put("token", token);
return ResponseEntity.ok(response);
} catch (AuthenticationException e) {
throw new BadCredentialsException("Invalid login or password");
}
}
}
User class:
#Getter
#Entity
#NoArgsConstructor
#Table(name = "users")
public class User {
// Fields
//
#Id
#GeneratedValue
private Long id;
private String name;
#Column(name = "last_name")
private String lastName;
private String login;
private String password;
private String mail;
private boolean isDeleted;
#Enumerated(EnumType.STRING)
private Role role;
}
#RequiredArgsConstructor
#Component
public class JwtTokenProvider {
// Fields
//
private final UserDetailsService userDetailsService;
#Value("${jwt.token.secret}")
private String secret;
#Value("${jwt.token.expired}")
private Long validityInMilliSeconds;
//
// METHODS
//
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(8);
}
#PostConstruct
protected void init() {
secret = Base64.getEncoder().encodeToString(secret.getBytes());
}
/**
* Генерируем ТОКЕН
*
* #param login
* #param role
* #return ТОКЕН
*/
public String createToken(String login, Role role) {
Claims claims = Jwts.claims().setSubject(login);
claims.put("roles", getRoleName(role));
Date now = new Date();
Date validity = new Date(now.getTime() + validityInMilliSeconds);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(validity)
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
}
public Authentication getAuthentication(String token) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(getLogin(token));
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
}
public String getLogin(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody().getSubject();
}
public boolean validateToken(String token) {
try {
Jws<Claims> claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
if (claims.getBody().getExpiration().before(new Date())) {
return false;
}
return true;
} catch (JwtException | IllegalArgumentException e) {
throw new JwtAuthenticationException("JWT token is expired or invalid");
}
}
public String resolveToken(HttpServletRequest req) {
String bearerToken = req.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer_")) {
return bearerToken.substring(7, bearerToken.length());
}
return null;
}
private String getRoleName(Role role) {
String roleName = role.name();
return roleName;
}
}
#RequiredArgsConstructor
public class JwtTokenFilter extends GenericFilterBean {
private final JwtTokenProvider jwtTokenProvider;
#Override
public void doFilter(ServletRequest request,
ServletResponse response, FilterChain chain) throws IOException, ServletException {
String token = jwtTokenProvider.resolveToken((HttpServletRequest) request);
if (token != null && jwtTokenProvider.validateToken(token)) {
Authentication auth = jwtTokenProvider.getAuthentication(token);
if (auth != null) {
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
chain.doFilter(request, response);
}
}
#RequiredArgsConstructor
#Service
#Slf4j
public class JwtUserDetailsService implements UserDetailsService {
private final UserService userService;
#Override
public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
User user = userService.findByLogin(login);
JwtUser jwtUser = JwtUserFactory.create(user);
log.info("IN loadUserByUsername - user with login: {} successfully loaded", login);
return jwtUser;
}
}
#AllArgsConstructor
#Getter
public class JwtUser implements UserDetails {
// Fields
//
private Long id;
private String name;
private String lastName;
private String login;
private String password;
private String mail;
private boolean isDeleted;
private Role role;
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + role));
}
#Override
public String getUsername() {
return null;
}
#Override
public boolean isAccountNonExpired() {
return false;
}
#Override
public boolean isAccountNonLocked() {
return false;
}
#Override
public boolean isCredentialsNonExpired() {
return false;
}
#Override
public boolean isEnabled() {
return false;
}
}
P.S. I will not throw all the code for JWT from the project here, but skip only the most important thing.
LOGS:
2020-08-10 16:36:27.343 INFO 7556 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Completed initialization in 64 ms
2020-08-10 16:36:28.454 INFO 7556 --- [nio-8080-exec-2] r.c.security.JwtUserDetailsService : IN loadUserByUsername - user with login: art123 successfully loaded

How to imlement ReactiveUserDetailsService with MongoDB

There in Spring security 5 appear an interface as ReactiveUserDetailsService.
Now my question is how to implement UserDetailsService using:
Spring-data-Mongo
Spring Security (5)
That I have (users stored in memory)
#Bean
public MapReactiveUserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder().username("test").password("password").roles("USER").build();
UserDetails admin = User.withDefaultPasswordEncoder().username("admin").password("admin").roles("USER", "ADMIN").build();
return new MapReactiveUserDetailsService(user, admin);
}
What I want:
#Document(collection = "user")
public class User implements UserDetails {
#Id
private Long id;
private LocalDate createdAt;
private String username;
private String password;
private boolean accountNonLocker;
private boolean enabled;
#DBRef
private List<GrantedAuthority> grantedAuthorities;
//getters and setters
}
#Component
public class SecUserDetailsService implements ReactiveUserDetailsService {
}
And I don't know how to implement it. not found any resources on web.
Already found a solution:
Implement UserDetails interface in UseAccount class and create Role class that implements GrantedAuthority interface:
Role
#Data
#Document
public class Role implements GrantedAuthority {
#Id
private String id;
#Override
public String getAuthority() {
return id;
}
}
UseAcount
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Document
public class UserAccount implements UserDetails {
#Id
private String id;
private String username;
private String password;
private String firstName;
private String lastName;
private String idnp;
#Email
private String email;
#Builder.Default()
private boolean active = true;
#Builder.Default()
#DBRef
private List<Role> roles = new ArrayList<>();
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles;
}
#Override
public String getPassword() {
return password;
}
#Override
public String getUsername() {
return username;
}
#Override
public boolean isAccountNonExpired() {
return active;
}
#Override
public boolean isAccountNonLocked() {
return active;
}
#Override
public boolean isCredentialsNonExpired() {
return active;
}
#Override
public boolean isEnabled() {
return active;
}
#Override
public String getName() {
return firstName + " " + lastName;
}
}
Define Security configuration and in it create web filter.
#Configuration
#EnableWebFluxSecurity
public class SecurityConfiguration {
#Bean
SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) {
return http
.csrf().disable()
.authorizeExchange()
.pathMatchers("/login", "/logout").permitAll()
.pathMatchers("/i18n/**",
"/css/**",
"/fonts/**",
"/icons-reference/**",
"/img/**",
"/js/**",
"/vendor/**").permitAll()
.anyExchange()
.authenticated()
.and()
.formLogin()
.loginPage("/login")
.and()
.logout()
.logoutUrl("/logout")
.and()
.build();
}
//in case you want to encrypt password
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Create repository to extract your user from database.
And last step is to create UserDetailsService
#Component
public class SecUserDetailsService implements ReactiveUserDetailsService {
#Autowired
public ReactiveUserAccountRepository reactiveUserAccountRepository;
#Override
public Mono<UserDetails> findByUsername(String username) {
Mono<UserAccount> data = reactiveUserAccountRepository.findByUsername(username);
return data.cast(UserDetails.class);
}
}

Spring security Role cannot be converted to granted authority

User.java
public class User implements Serializable{
#Id
#Size(min=5,max=15)
#Column(name="username", unique=true)
private String username;
#OneToMany(mappedBy="user")
private Collection<Role> roles;
public User(User user) {
this.username=user.username;
this.roles=user.getRoles();
}
}
Role.java
public class Role implements Serializable{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String role;
#ManyToOne
#JoinColumn(name="username")
private User user;
}
UserServiceImpl.java
public class UserServiceImpl implements UserServiceDetails {
private UserRepo userRepo;
#Autowired
public void setUserRepo(UserRepo userRepo) {
this.userRepo = userRepo;
}
#Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
User user=userRepo.findUserByUsername(username);
if(user == null) {
throw new UsernameNotFoundException("Could not find user " + username);
}
CustomUser customUser=new CustomUser(user);
return customUser;
}
public final static class CustomUser extends User implements UserDetails
{
public CustomUser(User user) {
super(user);
}
#SuppressWarnings("unchecked")
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorities =
new ArrayList<GrantedAuthority>();
authorities.addAll((Collection<? extends GrantedAuthority>) getRoles());
return authorities;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
}
}
SecurityCOnfig.java
public class SecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth
.userDetailsService(userDetailsService);
}
}
Errors:
Role cannot be cast to org.springframework.security.core.GrantedAuthority
my roles table contain roles in the form string "user", "admin" etc
Any suggestions?
Thanks
As Denium says, casting won't work the way it's being done in your code. I do similar thing by looping through each role, like this:
authorities = new HashSet<GrantedAuthority>(roles.size());
for (String role : roles)
authorities.add(new SimpleGrantedAuthority("ROLE_" + role));

Categories