I need some additional data in in the user details of authenticated users. So i wrote a custom details service and as a second approach a custom authentication provider to enrich the data in the user object. But the principal object in the security context stays a string instead of becoming the desired user object and when i'm setting breakpoints im my custom details service and authentication porvider it looks like this code is never used by spring albeit my customized classes are listed in springs authentication manager builder.
This is my custom user details service:
package edu.kit.tm.cm.bamsg.bffweb.iamservice;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import java.util.HashSet;
import java.util.Set;
/*** #author schlund*/
public class CustomStudentDetailsService implements UserDetailsService {
private SecurityUserRepository securityUserRepository;
public CustomStudentDetailsService(SecurityUserRepository userSecurityRepository){
this.securityUserRepository=userSecurityRepository;
}
#Override
public SecurityUser loadUserByUsername(String kitID) throws UsernameNotFoundException {
try {
SecurityUser securityPerson = securityUserRepository.findByUsername(kitID);
if (securityPerson == null) {
return null;
}
return securityPerson;
}
catch (Exception e){
throw new UsernameNotFoundException("User not found");
}
}
private Set<GrantedAuthority> getAuthorities(SecurityUser securityPerson){
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(securityPerson.getRole());
authorities.add(grantedAuthority);
return authorities;
}
}
This is my custom authentication provider:
package edu.kit.tm.cm.bamsg.bffweb.iamservice;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
public Authentication authenticate(Authentication authentication ) throws AuthenticationException {
String password = authentication.getCredentials().toString().trim();
SecurityUser appUser = new SecurityUser();
return new UsernamePasswordAuthenticationToken(appUser, password, null);
}
#Override
public boolean supports(Class<? extends Object> authentication) {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
}
This is my web security config:
package edu.kit.tm.cm.bamsg.bffweb;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
import org.springframework.cloud.security.oauth2.client.feign.OAuth2FeignRequestInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import edu.kit.tm.cm.bamsg.bffweb.iamservice.*;
#Configuration
#EnableOAuth2Sso
#EnableGlobalMethodSecurity(prePostEnabled = true)
#ComponentScan("edu.kit.tm.cm.bamsg.bffweb.iamservice")
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static final String REALM = "bam";
#Autowired
private CustomAuthenticationProvider authProvider;
#Autowired
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.logout()
.and()
//endpoints without authentication
.authorizeRequests().antMatchers("/logged", "/userData").permitAll()
.and()
// default with authentication
.authorizeRequests().anyRequest().authenticated()
.and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
#Bean
public OAuth2FeignRequestInterceptor oAuth2FeignRequestInterceptor(OAuth2ClientContext context, OAuth2ProtectedResourceDetails details) {
return new OAuth2FeignRequestInterceptor(context, details);
}
#Bean
BasicAuthenticationEntryPoint getBasicAuthEntryPoint() {
BasicAuthenticationEntryPoint basicAuth = new BasicAuthenticationEntryPoint();
basicAuth.setRealmName(REALM);
return basicAuth;
}
}
And at least after authentication at the code line with the System.out.println the customized services should have been called, but unfortunatelly they are not. Breakpoints in the customized services have never been reached and the principal is still a string and not my customized user:
#ComponentScan("edu.kit.tm.cm.bamsg.bffweb.iamservice")
#RestController
#RequestMapping("/api/theses")
public class ThesisController {
#Autowired
private ThesisClient thesisClient;
#Autowired
private ThesisPersonLinker linker;
#Autowired
private ThesisPersonFilter filter;
#GetMapping
#PreAuthorize("hasRole('theses')")
public ResponseEntity<Collection<ThesisFrontendDTO>> findAllTheses() {
System.out.println(SecurityContextHolder.getContext().getAuthentication().getPrincipal());
The extended user class looks like that:
package edu.kit.tm.cm.bamsg.bffweb.iamservice;
import org.springframework.security.core.userdetails.User;
public class SecurityUser extends User{
String firstName;
String name;
String password;
private static final long serialVersionUID = 1L;
public SecurityUser() {
super("user", "none", null);
firstName = "Rainer";
name = "Schlund";
password = "meins";
}
public String getRole(){
return "Student";
}
}
The code contains some simplifications for testing like SecurityPerson always returning the same person, but i think that should not be a problem.
To address the problem of "principal object in the security context stays a string instead of becoming the desired user object" if you have gone through the Principal object it has getCreditantial() method returning object only , considering security user is principal object it is not providing enough information to become correct principal object.
Please take a look on UserDetailsPrincipal class for principal implementation :
public class UserDetailsPrincipal extends org.springframework.security.core.userdetails.User implements UserDetails {
/**
*
*/
private static final long serialVersionUID = 1L;
private Member user;
List<GrantedAuthority> authorities;
public UserDetailsPrincipal(Member user, List<GrantedAuthority> authorities ) {
super(user.getLogin(),user.getEncrytedPassword(),authorities);
this.authorities = authorities;
this.user = user;
}
// #Override
// public Collection<? extends GrantedAuthority> getAuthorities() {
// return this.authorities;
// }
#Override
public String getPassword() {
return user.getEncrytedPassword();
}
#Override
public String getUsername() {
return user.getLogin();
}
#Override
public boolean isAccountNonExpired() {
return !user.getIsExpired();
}
#Override
public boolean isAccountNonLocked() {
return !user.getIsLocked() || user.getIsLocked() == null;
}
#Override
public boolean isCredentialsNonExpired() {
return !user.getIsExpired() || user.getIsExpired() == null;
}
#Override
public boolean isEnabled() {
return user.getActive() == 1;
}
}
also used customAuthProvider like this :
#Slf4j
#Component("customAuthProvider")
#Transactional(readOnly = true,propagation = Propagation.REQUIRES_NEW)
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
#Qualifier("userDetailsServiceAdapter")
private UserDetailsServiceAdapter userDetailsService;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String login = authentication.getName();
String password = authentication.getCredentials().toString();
/* Member member = userRepository.findUserAccount(login); */
log.info("user for login inside custom auth service service : " + login);
if (!StringUtils.isEmpty(login) && !StringUtils.isEmpty(password)) {
try {
UserDetails userDetail = userDetailsService.loadUserByUsernameAndPassword(login, password);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userDetail,
userDetail.getPassword(), userDetail.getAuthorities());
token.setDetails(userDetail);
return token;
} catch (UsernameNotFoundException exception) {
return new UsernamePasswordAuthenticationToken(login, password, new ArrayList<>());
}
} else {
return new UsernamePasswordAuthenticationToken(login, password, new ArrayList<>());
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
If you want Spring security to use your Authentication provider you need to provide some entry point for providing auth credentials. Here is example of WebSecuritConfig class:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#ComponentScan
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static final String REALM = "realm";
#Autowired
private CustomAuthenticationProvider authProvider;
#Autowired
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.logout()
.and()
// default with authentication
.authorizeRequests().anyRequest().authenticated()
.and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and().httpBasic().realmName(REALM).authenticationEntryPoint(getBasicAuthEntryPoint());
}
#Bean
BasicAuthenticationEntryPoint getBasicAuthEntryPoint() {
BasicAuthenticationEntryPoint basicAuth = new BasicAuthenticationEntryPoint();
basicAuth.setRealmName(REALM);
return basicAuth;
}
}
And you need to change SecurityUser constructor, because you cannot pass null authorities to super constructor:
public SecurityUser() {
super("user", "none", new ArrayList<>());
firstName = "Rainer";
name = "Schlund";
password = "meins";
}
When you provide Authentication provider, UserDetailsService is not used. So you need to use it in auth provider.
Related
i am new to Spring Security, i just have a User with enum Role and enum permissions, i wanted to have a basic auth using postman and to test it , but i always get 401 status code.
I am not sure what is the problem exactly because no errors i receive or no exeption occured but all i know is that i can not log in with basic auth using postman perhaps my configuration is not perfect or UserDetails and UserDetailsServices are not like they should be or maybe capturing the authorities in UserDetails is not working at all.
or maybe my password is not encoded in database and that's why the authentication can not pass.
My ApplicationSecurityConfig:
`#Configuration
#EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
//private final PasswordEncoder passwordEncoder;
private final ApplicationUserDetailsService applicationUserDetailsService;
#Autowired
public ApplicationSecurityConfig(PasswordEncoder passwordEncoder,
ApplicationUserDetailsService applicationUserDetailsService) {
// this.passwordEncoder = passwordEncoder;
this.applicationUserDetailsService = applicationUserDetailsService;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/","/index","/css/*","/js/*") .permitAll()
//MEMBER
.antMatchers("/api/**").hasAnyRole(
ApplicationUserRole.SUPER_ADMIN.name(),
ApplicationUserRole.ADMIN.name(),
ApplicationUserRole.MEMBER.name()
)
.antMatchers(HttpMethod.GET,"/api/**").hasAnyAuthority(
ApplicationUserPermissions.SUPER_ADMIN_READ.name(),
ApplicationUserPermissions.ADMIN_READ.name(),
ApplicationUserPermissions.MEMBER_READ.name()
)
//ADMIN
.antMatchers("/admin/api/**").hasAnyRole(ApplicationUserRole.ADMIN.name(),ApplicationUserRole.SUPER_ADMIN.name())
.antMatchers(HttpMethod.POST,"/admin/api/**").hasAnyAuthority(
ApplicationUserPermissions.SUPER_ADMIN_WRITE.name(),
ApplicationUserPermissions.ADMIN_WRITE.name()
)
.antMatchers(HttpMethod.PUT,"/admin/api/**").hasAnyAuthority(
ApplicationUserPermissions.SUPER_ADMIN_WRITE.name(),
ApplicationUserPermissions.ADMIN_WRITE.name()
)
.antMatchers(HttpMethod.PATCH,"/admin/api/**").hasAnyAuthority(
ApplicationUserPermissions.SUPER_ADMIN_WRITE.name(),
ApplicationUserPermissions.ADMIN_WRITE.name()
)
.antMatchers(HttpMethod.DELETE,"/admin/api/**").hasAnyAuthority(
ApplicationUserPermissions.SUPER_ADMIN_WRITE.name(),
ApplicationUserPermissions.ADMIN_WRITE.name()
)
//SUPER_ADMIN
.antMatchers("/super/admin/api/**").hasAnyRole(
ApplicationUserRole.SUPER_ADMIN.name()
)
.antMatchers(HttpMethod.POST,"/super/admin/api/**").hasAuthority(
ApplicationUserPermissions.SUPER_ADMIN_WRITE.name()
)
.antMatchers(HttpMethod.PUT,"/super/admin/api/**").hasAuthority(
ApplicationUserPermissions.SUPER_ADMIN_WRITE.name()
)
.antMatchers(HttpMethod.PATCH,"/super/admin/api/**").hasAuthority(
ApplicationUserPermissions.SUPER_ADMIN_WRITE.name()
)
.antMatchers(HttpMethod.DELETE,"/super/admin/api/**").hasAuthority(
ApplicationUserPermissions.SUPER_ADMIN_WRITE.name()
)
.anyRequest()
.authenticated()
.and()
.httpBasic();
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(applicationUserDetailsService);
}`
ApplicationUserRole:
package com.github.workTimeMangementGithub.security;
import com.google.common.collect.Sets; import
org.springframework.security.core.GrantedAuthority; import
org.springframework.security.core.authority.SimpleGrantedAuthority;
import java.util.Set; import java.util.stream.Collectors;
public enum ApplicationUserRole {
SUPER_ADMIN(Sets.newHashSet(ApplicationUserPermissions.SUPER_ADMIN_READ,ApplicationUserPermissions.SUPER_ADMIN_WRITE)),
ADMIN(Sets.newHashSet(ApplicationUserPermissions.ADMIN_READ,ApplicationUserPermissions.ADMIN_WRITE)),
MEMBER(Sets.newHashSet(ApplicationUserPermissions.MEMBER_READ,ApplicationUserPermissions.MEMBER_WRITE));
private final Set<ApplicationUserPermissions> permissions;
ApplicationUserRole(Set<ApplicationUserPermissions> permissions) {
this.permissions = permissions;
}
public Set<ApplicationUserPermissions> getPermissions() {
return permissions;
}
public Set<GrantedAuthority> getGrantedAuthorities() {
Set<GrantedAuthority> permissions = getPermissions().stream().map(permission-> new
SimpleGrantedAuthority(permission.getPermission())).collect(Collectors.toSet());
permissions.add(new SimpleGrantedAuthority("ROLE_"+this.name()));
return permissions;
}
}
Here i have implemented User Role for Role Based Auth and i connect them with their permissions
My ApplicationUserPermissions
public enum ApplicationUserPermissions {
SUPER_ADMIN_WRITE("super_admin:write"),
SUPER_ADMIN_READ("super_admin:read"),
ADMIN_WRITE("admin:write"),
ADMIN_READ("admin:read"),
MEMBER_WRITE("member:write"),
MEMBER_READ("member:read");
private final String permission;
ApplicationUserPermissions(String permission) {
this.permission = permission;
}
public String getPermission() {
return permission;
}
}
Here i Created the permissions for every User Role to determine all permissions and privileges for each role.
My ApplicationUserDetailsService
import java.util.Optional;
#Service
#Slf4j
public class ApplicationUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
#Autowired
public ApplicationUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<User> user = Optional.of(userRepository.findUserByUsername(username));
if(user.get() != null){
UserDTO userDto = UserMapper.toDTO(user.get());
log.info("User Found "+ userDto.getUsername());
}else {
log.warn("User NOT Found ");
}
user.orElseThrow(() -> new UsernameNotFoundException("Not found: " + username));
return new ApplicationUserDetails(user.get());
}
}
Here i have implemented ApplicationUserDetailsService and called the method loadUserByUsername with handling UserNotFoundException in case the user is not found.
My ApplicationUserDetails:
#Slf4j
public class ApplicationUserDetails implements UserDetails {
private List<? extends GrantedAuthority> grantedAuthorities;
private String username;
private String password;
private boolean isAccountNonExpired;
private boolean isAccountNonLocked;
private boolean isCredentialsNonExpired;
private boolean isEnabled;
public ApplicationUserDetails(List<? extends GrantedAuthority> grantedAuthorities, String username, String password, boolean isAccountNonExpired, boolean isAccountNonLocked, boolean isCredentialsNonExpired, boolean isEnabled) {
this.grantedAuthorities = grantedAuthorities;
this.username = username;
this.password = password;
this.isAccountNonExpired = isAccountNonExpired;
this.isAccountNonLocked = isAccountNonLocked;
this.isCredentialsNonExpired = isCredentialsNonExpired;
this.isEnabled = isEnabled;
}
public ApplicationUserDetails(User user) {
List<? extends GrantedAuthority> authorities = new ArrayList<>(ApplicationUserRole.ADMIN.getGrantedAuthorities());
this.grantedAuthorities = authorities;
log.warn("authorities "+authorities);
this.username = user.getUsername();
this.password = user.getPassword();
this.isAccountNonExpired = true;
this.isAccountNonLocked = true;
this.isCredentialsNonExpired = true;
this.isEnabled = true;
}
#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;
}
Here i have implemented ApplicationUserDetails and override some methods.
My problem is that i can not authenticate using basic auth via Postman.
Here a screen capture of the users of the database:
I am trying to find out what is wrong with my code , i follow many tutorials but no full example of working with enum Roles and permissions with JPA authentication , i spend a lot of time and i still don't know what is wrong exactly with my code.
The logger Slf4j is no showing the authenticated user in console and i don't know why.
Postman:
Spring Boot log Captures:
Any help will be so appreciated.
I'm doing a project in which you have to register some users and also giving them a rol (user by default). After I've registered a user, I added jwt auth and I was able to get the jwt response, but after trying to implement some filters on it, the code started to fail.
At this point I've commented the filter implementation method and also inside my WebSecurityConfig.
It's supposed that I'll receive a username and password inside my endpoint ("authenticate/").
#RequestMapping(
value = {"authenticate", "authenticate/"},
method = RequestMethod.POST,
consumes = {
MediaType.APPLICATION_FORM_URLENCODED_VALUE,
MediaType.MULTIPART_FORM_DATA_VALUE,
MediaType.APPLICATION_JSON_VALUE
},
produces = MediaType.APPLICATION_JSON_VALUE
)
public #ResponseBody AuthResponse login(AuthRequest authRequest) throws Exception
{
return this.authService.authenticate(authRequest);
}
AuthRequest is username and password.
#AllArgsConstructor
#NoArgsConstructor
#Setter
#Getter
public class AuthResponse
{
private String username;
private String password;
}
But the error is when I try to authenticate the user using the AuthenticationManager.
AuthService authenticate method
public AuthResponse authenticate(AuthRequest authRequest) throws Exception
{
//System.out.println(authRequest.getUsername() +"," + authRequest.getPassword() + "," + this.bCryptPasswordEncoder.encode(authRequest.getPassword()));
try {
this.authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword())
);
} catch (BadCredentialsException e) {
//Here is the error
throw new Exception("Incorrect username or password", e);
}
final UserDetails userDetails = usersDetailService.loadUserByUsername(authRequest.getUsername());
final String jwt = jwtUtil.generateToken(userDetails);
//this.logService.save(new Log(null, userDetails.getUsername(), jwt, null));
System.out.println(jwt);
return new AuthResponse(jwt);
}
Error inside postman console.
"timestamp": "2021-04-03T19:23:01.510+00:00",
"status": 403,
"error": "Forbidden",
"trace": "java.lang.Exception: Incorrect username or password\n\tat com.cncipo.nl.auth.service.AuthService.authenticate2(AuthService.java:60)\n\tat com.cncipo.nl.controller.SessionRestController.auth(SessionRestController.java:85)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:498)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:197)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:141)\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1060)\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:962)\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\n\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:652)\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:733)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:327)\n\tat org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:115)\n\tat org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:81)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)\n\tat org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119)\n\tat org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:113)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)\n\tat org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:126)\n\tat org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:81)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)\n\tat org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:105)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)\n\tat org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:149)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)\n\tat org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)\n\tat org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:103)\n\tat org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:89)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)\n\tat org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90)\n\tat org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)\n\tat org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:110)\n\tat org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:80)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)\n\tat org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:55)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)\n\tat org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:211)\n\tat org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:183)\n\tat org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358)\n\tat org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374)\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893)\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1707)\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\n\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)\n\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\n\tat java.lang.Thread.run(Thread.java:748)\nCaused by: org.springframework.security.authentication.BadCredentialsException: Bad credentials\n\tat org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:141)\n\tat org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:182)\n\tat org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:201)\n\tat org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$AuthenticationManagerDelegator.authenticate(WebSecurityConfigurerAdapter.java:518)\n\tat com.cncipo.nl.auth.service.AuthService.authenticate2(AuthService.java:56)\n\t... 86 more\n",
"message": "Access Denied",
"path": "/api/authenticate"
}
I'll add the WebSecurityConfig and the JwtFilter in case there is something that I missed and it's causing the error.
JwtFilter
//#Component
public class JwtRequestFilter extends OncePerRequestFilter
{
#Autowired
private UsersDetailService usersDetailService;
#Autowired
private JwtUtil jwtUtil;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException
{
/* Get the header for auth */
final String authHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
System.out.println("Flag of header it's null when I implemented" + authHeader);
/* Extract jwt and username */
if (authHeader != null && authHeader.startsWith("Bearer "))
{
jwt = authHeader.substring(7);
username = jwtUtil.extractUsername(jwt);
}
/* Extract user details */
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null)
{
UserDetails userDetails = this.usersDetailService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, userDetails))
{
/* Create default new jwt auth token */
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
/* Handling the control to the next filter chain */
filterChain.doFilter(request, response);
}
}
WebSecurityConfig
#EnableWebSecurity
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{
#Autowired
private UsersDetailService usersDetailService;
//#Autowired
//private JwtRequestFilter jwtRequestFilter;
#Bean
public BCryptPasswordEncoder passwordEncoder()
{
return new BCryptPasswordEncoder();
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(usersDetailService);
}
/* Bean of AuthManager due to it used to work in older version of spring */
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception
{
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception
{
/* Disable security */
http.csrf().disable();
/* User info page requires login as admin and user role. If no login, redirect */
http.authorizeRequests().antMatchers("users").access("hasAnyRole('admin', 'user')");
/* Only for admin */
http.authorizeRequests().antMatchers("logs", "admin").access("hasRole('admin')");
/* The pages does not require login/auth */
http.authorizeRequests().antMatchers("login", "logout", "/authenticate/, login/")
.permitAll();//.anyRequest().authenticated();
/* Using sessiong managment for intercept jwt auth . Spring won't create session, jwt will manage them*/
//http.exceptionHandling().and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
//http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}
UsersDetailService, MyUserDetails implements UserDetails from import org.springframework.security.core.userdetails.UserDetails;
#Service
public class UsersDetailService implements UserDetailsService
{
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
{
User user = this.userRepository.getUserByUsername_user(username);
if (user == null) { throw new UsernameNotFoundException("Could not find user"); }
return new MyUserDetails(user);
}
}
Try the following settings:
In your SecurityConfig
#Autowired
private JwtFilterRequest jwtFilterRequest;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers("/forgetPassword").permitAll()
.antMatchers("/registerUser").permitAll()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and().exceptionHandling().and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and().formLogin().loginPage("/login").loginProcessingUrl("/login")
.defaultSuccessUrl("/index.html").failureUrl("/login?error")
.and().logout().logoutUrl("/logout");
http.addFilterBefore(this.jwtFilterRequest, UsernamePasswordAuthenticationFilter.class);
}
Filter Class JWT (JwtFilterRequest):
import cl.project.admin.security.jwt.JwtUtil;
import cl.project.admin.security.service.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
#Component
public class JwtFilterRequest extends OncePerRequestFilter {
#Autowired
private JwtUtil jwtUtil;
#Autowired
private CustomUserDetailsService userDetailsService;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authorizationHeader = request.getHeader("Authorization");
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
String jwt = authorizationHeader.substring(7);
String username = this.jwtUtil.extractUsername(jwt);
if(username != null && SecurityContextHolder.getContext().getAuthentication() == null){
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if(this.jwtUtil.validateToken(jwt, userDetails)){
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
}
filterChain.doFilter(request, response);
}
}
Your Custom UserDetailService:
import cl.project.admin.controllers.AttentionController;
import cl.project.admin.models.dao.IUserDao;
import cl.project.admin.models.entity.User;
import cl.project.admin.models.service.UserServiceImpl;
import org.apache.log4j.Logger;
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;
import java.util.ArrayList;
#Service
public class CustomUserDetailsService implements UserDetailsService {
Logger logger = Logger.getLogger(CustomUserDetailsService.class);
#Autowired
private UserServiceImpl userService;
#Override
#Transactional(readOnly = true)
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.findByUsername(username);
if(user == null) {
this.logger.error("Error al ingresar: " + username);
throw new UsernameNotFoundException(username);
}
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), new ArrayList<>());
}
}
JWT Util
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
#Service
public class JwtUtil {
#Value("${keyJWTSpringSecurity}")
private static final String KEY = "passwordInYourApplicationProperties";
public String generateToken(UserDetails userDetail){
return Jwts.builder().setSubject(userDetail.getUsername()).setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 1000*60*60*5))
.signWith(SignatureAlgorithm.HS256, KEY).compact();
}
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
private Claims extractAllClaims(String token) {
return Jwts.parser().setSigningKey(KEY).parseClaimsJws(token).getBody();
}
private Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
public String generateToken(String username) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, username);
}
public String generateTokenInfinity(String username) {
Map<String, Object> claims = new HashMap<>();
return createTokenInfinity(claims, username);
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 5))
.signWith(SignatureAlgorithm.HS256, KEY).compact();
}
private String createTokenInfinity(Map<String, Object> claims, String subject) {
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 800))
.signWith(SignatureAlgorithm.HS256, KEY).compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}
I'm trying to build a spring boot rest API with JWT role-based authentication, I'm stuck at the login part in spring security.
I'm currently using spring boot, spring data JPA (hibernate under the hood ), and Oracle 11g database.
All the tables get created and I can sign up but can't login.
WebSecurityConfig.java
import org.springframework.context.annotation.*;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.dao.*;
import org.springframework.security.config.annotation.authentication.builders.*;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.*;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private UserDetailsServiceImpl userDetailsService;
private BCryptPasswordEncoder bCryptPasswordEncoder;
#Bean
public UserDetailsService userDetailsService() {
return new UserDetailsServiceImpl();
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService());
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.POST,"/users/**").permitAll()
.antMatchers("/roles").hasAnyAuthority("ADMIN")
.anyRequest().authenticated()
.and().addFilter(new JWTAuthorizationFilter(authenticationManager()))
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
// this disables session creation on Spring Security
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
UserDetails.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
public class UserDetails implements org.springframework.security.core.userdetails.UserDetails {
private User user;
#Autowired
private UsersRepository usersRepository;
public UserDetails(UsersRepository usersRepository) {
this.usersRepository = usersRepository;
}
public UserDetails(User user) {
this.user = user;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Set<Role> roles = user.getRoles();
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for (Role role : roles) {
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return authorities;
}
#Override
public String getPassword() {
return user.getPassword();
}
#Override
public String getUsername() {
return user.getUsername();
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return user.isEnabled();
}
}
UserDetailsServiceImpl.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private UsersRepository usersRepository;
#Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
User user = usersRepository.getUserByUsername(username);
System.out.println("Found user in repo : "+user.getUsername()+" "+user.getPassword()+" "+user.getRoles());
if (user == null) {
throw new UsernameNotFoundException("Could not find user");
}
return new UserDetails(user);
}
}
JWTAuthenticationFilter.java
import com.auth0.jwt.JWT;
import com.bte.ifrs_server.entities.User;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Date;
import static com.auth0.jwt.algorithms.Algorithm.HMAC512;
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest req,
HttpServletResponse res) throws AuthenticationException {
System.out.println("Attempting authentication");
try {
User creds = new ObjectMapper()
.readValue(req.getInputStream(), User.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
creds.getUsername(),
creds.getPassword(),
new ArrayList<>())
);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
#Override
protected void successfulAuthentication(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain,
Authentication auth) throws IOException, ServletException {
System.out.println("Successfull Auth !!");
String token = JWT.create()
.withSubject(((User) auth.getPrincipal()).getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.sign(HMAC512(SECRET.getBytes()));
//Printing the access token into the response
PrintWriter out = res.getWriter();
res.setContentType("application/json");
res.setCharacterEncoding("UTF-8");
//Creating access token object to return it as a response
AccessToken accessToken=new AccessToken(HEADER_STRING,TOKEN_PREFIX,token);
//Set the access token as a JSON response body
Gson gson = new Gson();
String access_token=gson.toJson(accessToken);
out.print(access_token);
out.flush();
//Adding the access token to response header
res.addHeader(HEADER_STRING, TOKEN_PREFIX + token);
}
}
JWTAuthorizationFilter.java
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
public JWTAuthorizationFilter(AuthenticationManager authManager) {
super(authManager);
}
#Override
protected void doFilterInternal(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain) throws IOException, ServletException {
String header = req.getHeader(HEADER_STRING);
if (header == null || !header.startsWith(TOKEN_PREFIX)) {
chain.doFilter(req, res);
return;
}
UsernamePasswordAuthenticationToken authentication = getAuthentication(req);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(req, res);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String token = request.getHeader(HEADER_STRING);
if (token != null) {
// parse the token.
String user = JWT.require(Algorithm.HMAC512(SECRET.getBytes()))
.build()
.verify(token.replace(TOKEN_PREFIX, ""))
.getSubject();
if (user != null) {
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
}
return null;
}
return null;
}
}
AccessToken.java
public class AccessToken {
String header,prefix,value;
public AccessToken(String header, String prefix, String value) {
this.header = header;
this.prefix = prefix;
this.value = value;
}
}
SecurityConstants.java
import java.util.Arrays;
import java.util.List;
public class SecurityConstants {
public static final String SECRET = "SecretKeyToGenJWTs";
public static final long EXPIRATION_TIME = 864_000_000; // 10 days
public static final String TOKEN_PREFIX = "Bearer ";
public static final String HEADER_STRING = "Authorization";
public static final String SIGN_UP_URL = "/users/sign-up";
public static final List<String> PUBLIC_ROUTES = Arrays.asList("/users/sign-up" , "/users/login" , "/roles/**");
}
Role.java
import javax.persistence.*;
#Entity
#Table(name = "roles")
public class Role {
#Id
#Column(name = "role_id")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "id_generator")
#SequenceGenerator(name="id_generator", sequenceName = "role_id_sequence",allocationSize = 1)
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
User.java
import java.util.*;
import javax.persistence.*;
#Entity
#Table(name = "users")
public class User {
#Id
#Column(name = "user_id")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "id_generator")
#SequenceGenerator(name="id_generator", sequenceName = "user_id_sequence",allocationSize = 1)
private Long id;
private String username;
private String password;
private boolean enabled;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(
name = "users_roles",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id")
)
private Set<Role> roles = new HashSet<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
}
and the main app:
IfrsServerApplication.java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
#EnableJpaRepositories
#SpringBootApplication
public class IfrsServerApplication {
public static void main(String[] args) {
SpringApplication.run(IfrsServerApplication.class, args);
}
}
The code compiles and the server runs I can signup but authentication returns 403 after attempting to login ('/login').
Any Help will be appreciated. Thanks in advance.
You've shared quite a bit of code, so there may be other issues here, but one that I'll point out is that in your JWTAuthorizationFilter, you are not granting any authorities to the user:
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
The last parameter is what authorities the user has.
Configurations like:
.antMatchers("/roles").hasAnyAuthority("ADMIN")
will always return a 403 in that case.
The first solution I'd recommend is using Spring Security's built-in support for JWTs instead of rolling your own. There's a JWT login sample that looks quite similar to what you are trying to achieve.
Alternatively, you can try changing how you are calling that constructor so that you grant a list of authorities (like new SimpleGrantedAuthority("ADMIN")). The downside here is that you'll have a lot more code to maintain.
This question already has answers here:
Springboot Security hasRole not working
(3 answers)
Closed 3 years ago.
I am working on a spring security based web application in which I want to limit the items on the side bar based upon whether the logged in user has role ADMIN or USER. As far as authentication is concerned, everything is working fine but roles are not working as expected.
Following this post
For example -
<security:authorize access="hasRole('ADMIN')">
<li class=" nav-item"><a href="<c:url value = "/mapview/list"/>"><i
class="fa fa-map-marker"></i><span class="menu-title" data-i18n="">MapView</span></a>
</li>
</security:authorize>
The above element never gets visible even though I log in as - ADMIN.
Can someone please help me here in understanding what is going wrong.
Security Config
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class AppSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Bean
public PasswordEncoder passwordEncoder() {
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
#Bean
public AuthenticationProvider authProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(new BCryptPasswordEncoder());
return provider;
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.authorizeRequests().antMatchers("/login", "/resource/**").permitAll()
.anyRequest().fullyAuthenticated()
.and()
.formLogin()
.loginPage("/login").usernameParameter("username").passwordParameter("password").permitAll()
.loginProcessingUrl("/doLogin").successForwardUrl("/postLogin").failureUrl("/loginFailed").and()
.logout().logoutUrl("/doLogout").logoutSuccessUrl("/logout").permitAll().and().csrf().disable();
}
}
UserDetailsImpl
public class UserDetailsImpl implements UserDetails {
private static final long serialVersionUID = 1L;
private Collection<SimpleGrantedAuthority> authorities;
private String username;
private String password;
private Boolean enabled = true;
public void setAuthorities(Collection<SimpleGrantedAuthority> authorities) {
this.authorities = authorities;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
#Override
public String getPassword() {
return password;
}
#Override
public String getUsername() {
return username;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return enabled;
}
UserDetailsService
#Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private UserService userService;
private Converter<User, UserDetails> userUserDetailsConverter;
#Autowired
#Qualifier(value = "userToUserDetails")
public void setUserUserDetailsConverter(Converter<User, UserDetails> userUserDetailsConverter) {
this.userUserDetailsConverter = userUserDetailsConverter;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userUserDetailsConverter.convert(userService.findByUserName(username));
}
}
UserToUserDetails
#Component
public class UserToUserDetails implements Converter<User, UserDetails>{
#Override
public UserDetails convert(User user) {
UserDetailsImpl userDetails = new UserDetailsImpl();
if (user != null) {
userDetails.setUsername(user.getUsername());
userDetails.setPassword(user.getEncryptedPassword());
userDetails.setEnabled(user.getEnabled());
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
user.getRoles().forEach(role -> {
authorities.add(new SimpleGrantedAuthority(role.getName()));
});
userDetails.setAuthorities(authorities);
}
return userDetails;
}
}
Controller
#SessionAttributes({ "currentUser" })
#Controller
public class HomeController {
//skipping other mappings
#RequestMapping(value = "/postLogin", method = RequestMethod.POST)
public String postLogin(Model model, HttpSession session) {
UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder
.getContext().getAuthentication();
validatePrinciple(authentication.getPrincipal());
String username = ((UserDetailsImpl) authentication.getPrincipal()).getUsername();
model.addAttribute("currentUser", username);
return "redirect:/dashboard";
}
}
User
#Entity
public class User extends Auditable<String> {
//skipping other details
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "user_role",
joinColumns = { #JoinColumn(name = "user_id") },
inverseJoinColumns = { #JoinColumn(name = "role_id") })
private Set<Role> roles = new HashSet<>();
}
Tables created in db-
user
role
user_role
EDIT 1
Just added this mapping inside my controller and Once I login, if i hit /test on browser, both the booleans show false. I have no idea why the roles are not being set.. :-(
#GetMapping(value = "/test")
public void test(SecurityContextHolderAwareRequestWrapper request) {
boolean b = request.isUserInRole("ADMIN");
System.out.println("ROLE_ADMIN=" + b);
boolean c = request.isUserInRole("USER");
System.out.println("ROLE_USER=" + c);
}
EDIT 2
But, at the same time, below code shows role as ADMIN
public void test(SecurityContextHolderAwareRequestWrapper request) {
for (GrantedAuthority authority : SecurityContextHolder.getContext().getAuthentication().getAuthorities()) {
String userRole = authority.getAuthority();
System.out.println(userRole);
}
}
This looks like information is getting lost somewhere between retrieval from database and returning a response to the browser (obviously). Good thing you have a debugger, so I would start by tracing each step from where you access your database data, until you return that information. Follow every single step until you find where is it being lost, and post back when you have narrowed it down to a single place. If not maybe we can start looking at your html/script/template engine, but only after we are sure info is reaching browser.
this is my first project with Spring and I have just started to create the login with Spring Security. I want some pages to be accessible only for the admin and not for the players. I've found some examples on the web and this mechanism works pretty well, I have this secured page that is protected by the login and it's forbidden when the user has no ROLE_ADMIN.
#PreAuthorize("hasAuthority('ROLE_ADMIN')")
#GetMapping("/secured/all")
public String securedHello() {
return "Secured Hello";
}
The problem is that testing my code I found out that Spring authenticates the admin (and the user as well) only checking the username. If I put the wrong password it allows me to enter anyway. I don't understand how this is possible, shouldn't Spring Security do all the authentication work by itself? I've seen somebody suggested to implement an authentication manager or something like that, but I don't understand why and how to insert it in my code. I'm stuck on this since two days, please any advice wuold be really appreciated.
These are my classes:
package model;
import java.io.IOException;
import javax.naming.AuthenticationException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.http.HttpStatus;
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.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import com.fasterxml.jackson.databind.ObjectMapper;
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
#EnableJpaRepositories(basePackageClasses = PlayersRepository.class)
#ComponentScan(basePackageClasses= CustomUserDetailsService.class)
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(getPasswordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
//http.csrf().disable();
http.authorizeRequests()
.antMatchers("**/secured/**").access("hasAuthority('ROLE_ADMIN')")
.anyRequest().permitAll()
.and()
.formLogin().permitAll();
}
private PasswordEncoder getPasswordEncoder() {
return new PasswordEncoder() {
#Override
public String encode(CharSequence charSequence) {
return charSequence.toString();
}
#Override
public boolean matches(CharSequence charSequence, String s) {
return true;
}
};
}
}
package model;
import java.util.ArrayList;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;
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;
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private PlayersRepository usersRepository;
#Autowired
private RoleRepository rolesRepository;
public CustomUserDetailsService(PlayersRepository usersRepository, RoleRepository rolesRepository) {
this.usersRepository=usersRepository;
this.rolesRepository=rolesRepository;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<Player> optionalUser = usersRepository.findByUsername(username);
optionalUser
.orElseThrow(() -> new UsernameNotFoundException("Username not found"));
Player user= optionalUser.get();
System.out.println(user);
return toUserDetails(new UserObject(user.getUsername(),user.getPassword(),user.getRole()));
}
private UserDetails toUserDetails(UserObject userObject) {
return User.withUsername(userObject.name)
.password(userObject.password)
.roles(userObject.role).build();
}
private static class UserObject {
private String name;
private String password;
private String role;
public UserObject(String name, String password, String role) {
this.name = name;
this.password = password;
this.role = role;
}
}
}
package model;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class CustomUserDetails extends Player implements UserDetails {
String role;
public CustomUserDetails(final Player user) {
super(user);
}
public CustomUserDetails(Optional<Player> user, String role) {
super(user);
this.role=role;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> list = new ArrayList<GrantedAuthority>();
list.add(new SimpleGrantedAuthority("ROLE_"+ role));
System.out.println(list);
return list;
}
#Override
public String getPassword() {
return super.getPassword();
}
#Override
public String getUsername() {
return super.getUsername();
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
}
Shouldn't Spring Security do all the authentication work by itself?
Yes, Spring Security does that for you using an AuthenticationManager.
I've seen somebody suggested to implement an authentication manager or something like that, but I don't understand why and how to insert it in my code.
You actually already have an AuthenticationManager, since you built one within the configure() method:
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(getPasswordEncoder());
}
So, what is exactly the reason this isn't working you may ask. Well, the AuthenticationManager you provided contains two parts:
A part that fetches the user information (CustomUserDetailsService)
Another part that checks the password (the getPasswordEncoder()).
What happens behind the screens is that Spring calls your CustomUserDetailsService to fetch your user information, including your (hashed) password. After fetching that information, it calls your PasswordEncoder.matches() function to verify if the raw entered password matches your hashed password provided by the CustomUserDetailsService.
In your case, your PasswordEncoder.matches() function looks like this:
#Override
public boolean matches(CharSequence charSequence, String s) {
return true;
}
This means that regardless of what password you provide, it will return true. This is exactly what you're experiencing since any password will work.
So, how do you solve this? Well, your PasswordEncoder should actually hash your raw password and compare it to the hashed password that is being passed, for example:
#Override
public boolean matches(CharSequence rawPassword, String hashedPassword) {
String hashedPassword2 = null; // hash your rawPassword here
return hashedPassword2.equals(hashedPassword);
}
The implementation of this method depends on how you store your password in your database. Spring Security already comes with a few implementation including BcryptPasswordEncoder, StandardPasswordEncoder, MessageDigestPasswordEncoder, ... . Some of these implementations are deprecated, mostly to indicate that the hashing mechanisms used by those encoders are considered unsafe. There are no plans at the moment of writing to remove those encoders, as mentioned by the Javadoc:
Digest based password encoding is not considered secure. Instead use an adaptive one way function like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or SCryptPasswordEncoder. Even better use DelegatingPasswordEncoder which supports password upgrades. There are no plans to remove this support. It is deprecated to indicate that this is a legacy implementation and using it is considered insecure.
(Emphasis is my own)
If you are free to choose which implementation you pick, then Spring recommends using BCryptPasswordEncoder as mentioned by the Javadoc:
Service interface for encoding passwords. The preferred implementation is BCryptPasswordEncoder.
I just had a Quick scan I found this
private PasswordEncoder getPasswordEncoder() {
return new PasswordEncoder() {
#Override
public String encode(CharSequence charSequence) {
return charSequence.toString();
}
#Override
public boolean matches(CharSequence charSequence, String s) {
return true;
}
};
}
In your matches you are returning always true.
I guess here you should put logic for checking password for equality something like this
#Override
public boolean matches(CharSequence charSequence, String s) {
return charSequence.toString.equals(s);
}
I would suggest you use something like this
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}