I can't seem to get my security configuration right. No matter what I do when using hasRole my endpoints always return 403.
Also I can't get anything to work unless I duplicate my antMatchers under both .requestMatchers() and .authorizeRequests(). I'm clearly missing something here.
Basically I want everything to require authentication but a few endpoints only to be accessable if the user is member of certain groups (for now just admin).
My security configuration is as follows. Everything beside hasRole works.
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
#Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.requestMatchers()
.antMatchers(HttpMethod.GET, "/v2/api-docs", "/swagger-resources/**", "/swagger-ui.html")
.antMatchers(HttpMethod.GET, "/users")
.and()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/v2/api-docs", "/swagger-resources/**", "/swagger-ui.html").permitAll()
.antMatchers(HttpMethod.GET, "/users").hasRole("ADMIN")
.anyRequest().authenticated();
}
// Inspiration: https://spring.io/blog/2015/06/08/cors-support-in-spring-framework#comment-2416096114
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers(HttpMethod.OPTIONS, "/**");
}
}
My AuthenticationConfiguration is as follows
#Configuration
#EnableResourceServer
public class AuthenticationConfiguration extends GlobalAuthenticationConfigurerAdapter {
private final UserDetailsService userService;
private final PasswordEncoder passwordEncoder;
public AuthenticationConfiguration(UserDetailsService userService, PasswordEncoder passwordEncoder) {
this.userService = userService;
this.passwordEncoder = passwordEncoder;
}
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userService)
.passwordEncoder(passwordEncoder);
}
}
My AuthorizationServerConfiguration is as follows
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
private final AuthenticationManager authenticationManager;
public AuthorizationServerConfiguration(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("html5")
.secret("password")
.authorizedGrantTypes("password")
.scopes("openid");
}
}
I'll happily post my user service and other stuff. But everything seems to work beside hasRole and Principal is loaded with the right authorities (roles). But please let me know if I should post any more code.
The entire source code can be found here.
Have you tried with "ROLE_ADMIN" rather than just "ADMIN"? Take a look at this for reference:
Spring security added prefix "ROLE_" to all roles name?
Following up on my comments to the question I'll provide sample OAuth2 Configuration classes I've used for testing. I always use two different webapps, because I want a clear line between auth server and resource server(and because it makes configurations so much harder....), so my example probably needs some adjustments when used in a single webapp.
Configuration for the auth server:
#EnableAuthorizationServer
#Configuration
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
private TokenStore tokenStore;
private DataSource dataSource;
#Autowired
public OAuth2Config(TokenStore tokenStore,
DataSource dataSource) {
this.tokenStore = tokenStore;
this.dataSource = dataSource;
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore);
}
#Configuration
public static class TokenStoreConfiguration {
#Bean
public TokenStore tokenStore(DataSource dataSource) {
return new JdbcTokenStore(dataSource);
}
}
}
Configuration for resource server:
#EnableResourceServer
#Configuration
public class OAuth2Config extends ResourceServerConfigurerAdapter {
public static final String PROPERTY_RESOURCE_ID = "com.test.oauth.resourceId";
private Environment environment;
private TokenStore tokenStore;
#Autowired
public OAuth2Config(Environment environment,
TokenStore tokenStore) {
this.environment = environment;
this.tokenStore = tokenStore;
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenStore(tokenStore)
.resourceId(environment.getProperty(PROPERTY_RESOURCE_ID))
.stateless(true);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/demo")
.access("hasRole('DEMO')")
.anyRequest().denyAll()
.and()
.formLogin().disable()
.logout().disable()
.jee().disable()
.x509().disable();
}
#Configuration
public static class TokenStoreConfiguration {
#Bean
public TokenStore tokenStore(DataSource dataSource) {
return new JdbcTokenStore(dataSource);
}
}
}
Obviously this requires that you have a DataSource bean configured. This implementation uses the default tables as provided by spring security OAuth2(they are far from ideal, but can be customized if required).
There are a few things you might want to adjust for your case(I'll leave the classes I provided as is for a reference if people might want to use it with JDBC):
Create only one bean of type TokenStore and use InMemoryTokenStore instead of JdbcTokenStore
replace the configuration for clients with your inMemory() implementation and remove all references to my autowired DataSource
Provide requestMatchers() before specifying authorizeRequests() in your resource server configuration. Depending on the order the configuration is processed and the filter chains are added this might be required to allow the oauth endpoints to be reached without requiring an OAuth token.
Edit: Seeing the answer by ritesh.garg I think that what I provided might not resolve your issues, but might help some figuring out where and how to start configuring Spring Security OAuth2(When I did it the first time I found it hard to do, because back then I couldn't find any clear examples, though this might have changed)
I had the same problem, I just forgot to implements getAuthorities() method from UserDetails (SpringSecurity class). Look my entity:
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
#Entity
#Table(name = "tb_user")
public class User implements UserDetails, Serializable {
private static final long serialVersionUID = -6519124777839966091L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
#Column(unique = true)
private String email;
private String password;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "tb_user_role",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id")
)
private Set<Role> roles = new HashSet<>();
public User() {
}
public User(Long id, String firstName, String lastName, String email, String password) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.password = password;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public void setPassword(String password) {
this.password = password;
}
public Set<Role> getRoles() {
return roles;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles.stream().map(role -> new SimpleGrantedAuthority(role.getAuthority()))
.collect(Collectors.toList());
}
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;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(id, user.id);
}
#Override
public int hashCode() {
return Objects.hash(id);
}
}
The method getAuthorities return null by default when you extends UserDetails class from security package, you need implement something like that:
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles.stream().map(role -> new SimpleGrantedAuthority(role.getAuthority()))
.collect(Collectors.toList());
}
I hope this help someone, sorry about my english errors! hehe
Related
I added spring security to my app. I would like to secure any endpoints, and open "/users" for all
User configuration:
#Entity
#Table(name = "users")
#AllArgsConstructor
#NoArgsConstructor
public class UserEntity implements UserDetails {
#Id
private String id;
private String login;
private String password;
private String role;
public UserEntity(UserCreateRequestModel userCreateRequestModel) {
this.id = UUID.randomUUID().toString();
this.login = userCreateRequestModel.getLogin();
this.password = PasswordService.codePassword(userCreateRequestModel.getPassword());
this.role = "USER";
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> list = new ArrayList<>();
list.add(new SimpleGrantedAuthority(role));
return new ArrayList<SimpleGrantedAuthority>();
}
#Override
public String getUsername() {
return login;
}
#Override
public String getPassword() {
return password;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
My custom WebSecurityConfigurerAdapter:
#Configuration
#EnableWebSecurity
#RequiredArgsConstructor
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
private final MyUserDetailsService myUserDetailsService;
#Override
public void configure(HttpSecurity http) throws Exception {
http
.cors().and().csrf().disable()
.authorizeRequests()
.antMatchers("/users", "/users/**").permitAll()
.anyRequest().hasAuthority("USER")
;
}
#Override
protected UserDetailsService userDetailsService() {
return myUserDetailsService;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
and next I added custom UserDetailsService
#Service
#RequiredArgsConstructor
public class MyUserDetailsService implements UserDetailsService {
private final UserEntityRepository userEntityRepository;
#Override
public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
System.out.println(userEntityRepository.findByLogin(login));
return userEntityRepository.findByLogin(login).orElseThrow((() -> new ObjectNotFoundException(login)));
}
and now I am getting problem. Endpoint for "/users" is open to all, I can send request with any problem. But when I am trying for example any #PostMapping on endpoint "/shapes"
I am getting 403 status response. In postman I think everything is OK:
of course user exists in database. In UserEntity user isEnabled, isAccountNonExpired, isAccountNonLocked and isCredentialsNonExpired - everything is on true.
In your getAuthorities() method of UserEntity class you're returning an empty List, so spring-security sees no authorities (roles) for an authenticated user.
Change this method like this:
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.singletonList(new SimpleGrantedAuthority(role));
}
#ComponentScan(basePackages = {"conf"})
#ComponentScan(basePackages = {"application.controller"})
#ComponentScan(basePackages = {"applicaion.model"})
#ComponentScan(basePackages = {"applicaion.dao"})
#ComponentScan(basePackages = {"usersDetails"})
#SpringBootApplication
#EnableJpaRepositories
#EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Security config part
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Bean
#Override
public UserDetailsService userDetailsService() {
return super.userDetailsService();
}
#Override
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.permitAll();
}
#Bean
public PasswordEncoder passwordEncoder() {return NoOpPasswordEncoder.getInstance();}
}
User Entity
"felhasznalonev"==username and "felhasznalo"==user
in hungarian
in the database table has theese names
#Entity
#Table( name="felhasznalo")
public class User {
#Id
#GeneratedValue
private int id;
#Column( unique=true, nullable=false )
private String felhasznalonev;
#Column( nullable=false )
private String jelszo;
private int statusz;
public User() {}
public User(String felhasznalonev,String jelszo,int statusz) {
this.felhasznalonev=felhasznalonev;
this.jelszo=jelszo;
this.statusz=statusz;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getFelhasznalonev() {
return felhasznalonev;
}
public void setFelhasznalonev(String email) {
this.felhasznalonev = email;
}
public String getJelszo() {
return this.jelszo;
}
public void setPassword(String password) {
this.jelszo = password;
}
#Override
public String toString() {
return null;
}
public int getStatusz() {
return statusz;
}
public void setStatusz(int statusz) {
this.statusz = statusz;
}
}
userServiceimpl part
#Service("userDetailsService")
public class UserServiceImpl implements UserService, UserDetailsService {
#Autowired
private UserRepository userRepository;
#Autowired
public UserServiceImpl(UserRepository userRepository){
this.userRepository = userRepository;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = findByUsername(username);
return new UserDetailsImpl(user);
}
#Override
public User findByUsername(String username) {
return userRepository.findByUsername(username);
}
}
UserDetailsImpl part
public class UserDetailsImpl implements UserDetails {
private User user;
public UserDetailsImpl(User user) {
this.user = user;
}
public UserDetailsImpl() {}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Arrays.asList(new SimpleGrantedAuthority("USER"));
}
#Override
public String getPassword() {
return user.getJelszo();
}
#Override
public String getUsername() {
return user.getFelhasznalonev();
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
}
UserService part
public interface UserService {
public User findByUsername(String username);
}
UserRepository
public interface UserRepository extends JpaRepository<User,Integer> {
User findByUsername(String username);
}
When i run the code everything looks fine, the basic login page come in, i enter the username/password from the database but nothing happen
and IntellIj write this:
2021-11-25 13:12:48.870 ERROR 13928 --- [nio-8080-exec-5] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Filter execution threw an exception] with root cause
java.lang.StackOverflowError: null
at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$UserDetailsServiceDelegator.loadUserByUsername(WebSecurityConfigurerAdapter.java:472) ~[spring-security-config-5.3.4.RELEASE.jar:5.3.4.RELEASE]
at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$UserDetailsServiceDelegator.loadUserByUsername(WebSecurityConfigurerAdapter.java:472) ~[spring-security-config-5.3.4.RELEASE.jar:5.3.4.RELEASE]
-||-
the connection with database is good, i can list users as well
Thanks for reading all this and sorry for bad english and mistakes, have a good day!
java.lang.StackOverflowError error tell you method declaration in service layer is not linked with any JpaRepository. Problem is came up from loadUserByUsername method in userServiceimpl. You declare method findByUsername without linked with Repository.
Change
User user = findByUsername(username);
To
User user = userRepository.findByUsername(username);
And UserServiceImpl Implements with UserDetailsService only. You need to change inSecurity config code because it has more problem like add wrong annotation and two method declare with same name etc...
Modified Security config
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
private UserDetailsService userDetailsService;
#Bean
public AuthenticationProvider authProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder());
return provider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.permitAll();
}
#Bean
public PasswordEncoder passwordEncoder()
{
return new BCryptPasswordEncoder();
}
}
You have doubly declared userDetailsService with the same name,
First:
#Bean
#Override
public UserDetailsService userDetailsService() {
return super.userDetailsService();
}
Second:
#Service("userDetailsService")
public class UserServiceImpl implements UserService, UserDetailsService {
It may cause the problem. You should have only one instance of userDetailService.
In your SecurityConfig Can you try removing
#Bean
#Override
public UserDetailsService userDetailsService() {
return super.userDetailsService();
}
And changing the implementation for
#Override
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
to
#Autowired
private UserDetailsService userDetailsService;
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
I am learning spring security and has followed few tutorials on Youtube, I have completed the task as taught by the author/teacher but unfortunately i could not login when i try to access my urls for /user and /admin after login, though i receive granted authorities object from database with USER_USER and USER_ADMIN roles but still when i request those urls i throws exception for forbidden access, anyone can guide why this is happening?
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
#Autowired
private MyUserDetailsService userDetailsService;
/*Authentication method*/
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//auth.inMemoryAuthentication().withUser("admin").password("admin").roles("Admin").and().withUser("user").password("user").roles("User");
auth.userDetailsService(userDetailsService);
}
// Authorization - Should be from most secure to least one
#Override
protected void configure(HttpSecurity http) throws Exception {
// To allow access to any url without permission is by using permitAll() method
System.out.println("Accessign URL : ");
http.authorizeRequests().
antMatchers("/admin").hasRole("USER_ADMIN").
antMatchers("/user").hasAnyRole("USER_USER", "USER_ADMIN").
antMatchers("/", "static/css", "static/js").
permitAll().
and().
formLogin();
}
#Bean
public PasswordEncoder getPasswordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
MyUserDetails Class :
package com.springsecurity.demo.models;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
public class MyUserDetails implements UserDetails {
private static final long serialVersionUID = -3042145577630945747L;
private String userName;
private String password;
private List<GrantedAuthority> authorityList;
public MyUserDetails() {
}
public MyUserDetails(User user) {
this.userName = user.getUserName();
this.password = user.getPassword();
this.authorityList = Arrays.stream(user.getUserRole().trim().split(",")).map(SimpleGrantedAuthority::new).collect(Collectors.toList());
System.out.println((this.authorityList.size() > 0 ? this.authorityList.get(0) : "Empty"));
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorityList;
}
#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 true;
}
}
MyUserDetailsService class :
#Service
public class MyUserDetailsService implements UserDetailsService {
private UserRepository userRepository;
public MyUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
Optional<User> user = userRepository.findByUserName(userName);
user.orElseThrow(() -> new UsernameNotFoundException("User not found with name : " + userName));
return user.map(MyUserDetails::new).get();
}
}
UserRepository Class :
#Repository
public interface UserRepository extends JpaRepository<User, Integer> {
Optional<User> findByUserName(String userName);
}
Controller Class :
#RestController
public class GreetingController {
#RequestMapping(value = "/")
public String greet() {
return "Hello World!";
}
#RequestMapping(value = "/user")
public String greetUser() {
return ("<h1>Hello User!</h2");
}
#RequestMapping(value = "/admin")
public String greetAdmin() {
return ("<h1>Hello Admin!</h2");
}
}
Thanks
I found the answer with just updating my database user_roles column values from USER_USER & USER_ADMIN to ROLE_USER & ROLE_ADMIN it worked for me, I don't know exactly the reason but it was specified that SimpleGrantedAuthority class expects to have role names like Role_Admin & Role_User format and it worked perfectly for me.
Along with database change i updated my WebSecurity configure method to following,
protected void configure(HttpSecurity http) throws Exception {
// To allow access to any url without permission is by using permitAll() method
System.out.println("Accessign URL : ");
http.authorizeRequests().
antMatchers("/admin", "/api/v1/users").hasRole("ADMIN").
antMatchers("/api/v1/students", "/api/v1/courses").hasAnyRole("USER", "ADMIN").
antMatchers("/", "static/css", "static/js").
permitAll().
and().
formLogin();
}
I am learning how to use Spring and how to implement Spring Security in my project, using Roles and MongoDB.
I have a User model with a role list, and I want to let only ADMIN user to use some endpints from Controller.
Here is the User class and Role enum:
#Document(collection = "Users")
public class User {
#Id
private String id;
private String firstName;
private String lastName;
private String email;
private String password;
private List<Role> roles;
//constructor + getters and setters
}
public enum Role {
ADMIN,
BASIC_USER
}
I use the this UserDetails implementation: I think here is the problem...
public class CustomUserDetails implements UserDetails {
private User user;
public CustomUserDetails() {
}
public CustomUserDetails(User user) {
this.user = user;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.user.getRoles().stream().map(role -> new SimpleGrantedAuthority(String.format("ROLE_%s", role))).collect(Collectors.toList());
}
#Override
public String getPassword() {
return this.user.getPassword();
}
#Override
public String getUsername() {
return this.user.getEmail();
}
#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 User getUser() {
return user;
}
}
The UserDetailsService looks like this:
public class CustomUserDetailsServiceImpl implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
final User user = userRepository.findByEmail(email);
if (Objects.isNull(user)) {
throw new UsernameNotFoundException("User not found");
}
return new CustomUserDetails(user);
}
}
I made this configuration:
#Configuration
#EnableWebSecurity(debug = true)
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(createUserDetailsService()).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/users/**").permitAll()
.antMatchers( "/users/**").authenticated().anyRequest().hasAnyRole("ADMIN")
.antMatchers("/users/me").authenticated().anyRequest().hasAnyRole("ADMIN", "BASIC_USER")
.antMatchers( "/users/**").permitAll()
.and()
.formLogin().disable()
.httpBasic();
}
#Override
public void configure(WebSecurity web) throws Exception {
/* To allow Pre-flight [OPTIONS] request from browser */
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
}
/**
* Create password encoder bean used for encrypting the password
*
* #return
*/
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* Create user service bean used for find the user by email
* #return
*/
#Bean
public UserDetailsService createUserDetailsService() {
return new CustomUserDetailsServiceImpl();
}
}
When I make any call with Postman at localhost:8080/users using Basic auth with the admin details from DB, all the time I get 401 Unauthorized Status
I think the problem is that I use an Enum for Roles and I don't know how to correctly build UserDetails implementation.
If helps, this is the UserController
#RestController
#RequestMapping("/users")
public class UserController {
#Autowired
private UserService userService;
#GetMapping(path = "")
public List<User> getUsers(){
return this.userService.getUsers();
}
}
When I used MySQL for this project (With the same classes for security) the apps worked perfectly. In that implementation I used Roles, Users and Users_Roles tables.
I was following some tutorial for basic authentication using OAuth2. Whole thing works, but tests no. There are even simple class unit tests not working. I've got:
No qualifying bean of type org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder'.
AuthorizationServerConfiguration:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private AuthenticationManager authenticationManager;
#Autowired
public AuthorizationServerConfig(AuthenticationManager authenticationManager, PasswordEncoder passwordEncoder){
this.authenticationManager = authenticationManager;
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("clientId")
.authorizedGrantTypes("client_credentials", "password")
.authorities("ROLE_CLIENT","ROLE_TRUSTED_CLIENT").scopes("read","write","trust")
.resourceIds("oauth2-resource").accessTokenValiditySeconds(3600).secret("secret");
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.checkTokenAccess("isAuthenticated()");
}
}
UserDetails implementation:
public class CustomUserDetails implements UserDetails {
private static final long serialVersionUID = -3962786160780714576L;
private Collection<? extends GrantedAuthority> authorities;
private String password;
private String username;
private boolean enabled;
public CustomUserDetails(User user) {
this.username = user.getUsername();
this.password = user.getPassword();
this.authorities = translate(user.getRoles());
this.enabled = user.getEnabled();
}
private Collection<? extends GrantedAuthority> translate(List<Role> roles) {
List<GrantedAuthority> authorities = new ArrayList<>();
roles.stream().forEach(x -> authorities.add(new SimpleGrantedAuthority(x.getName())));
return authorities;
}
#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;
}
}
ResourceServerConfigurerAdapter:
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.headers().frameOptions().disable().and()
.authorizeRequests()
.antMatchers("/","/home","/register","/login").permitAll()
.antMatchers("/dev/hello-world").authenticated();
}
}
#SpringBootApplication
public class LimeApplication {
public static void main(String[] args) {
SpringApplication.run(LimeApplication.class, args);
}
Main class:
#SpringBootApplication
public class SomeApplication {
public static void main(String[] args) {
SpringApplication.run(SomeApplication.class, args);
}
#Autowired
public void authenticationManager(AuthenticationManagerBuilder builder, IUserDAO dao, PasswordEncoder encoder) throws Exception {
builder.userDetailsService(userDetailsService(dao)).passwordEncoder(encoder);
}
private UserDetailsService userDetailsService(final IUserDAO dao) {
return username -> new CustomUserDetails(dao.findByUsername(username));
}
}
application.yml
security:
oauth2:
resource:
filter-order: 3
I was looking and I was trying to disable authentication in test/resources/application.yml by
security:
basic:
enabled: false
But this does not work. I cannot find any answer how to correct or mock this. Can anyone help me? Does anyone know how to mock it? Or get this bean in tests?
Edit:
Tests are running with #DataJpaTest #RunWith(SpringRunner.class) annotations