I've multiple users which I want to configure into spring-boot's UserDetailsService for basic authentication.
User has a additional field id tagged with it.
import org.springframework.security.core.userdetails.UserDetails;
public class User implements UserDetails {
private final String username;
private final String password;
private final String id;
private static final String ROLE_USER = "ROLE_USER";
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(ROLE_USER);
return Stream.of(simpleGrantedAuthority).collect(Collectors.toList());
}
// Getter & setter
}
Properties yml looks like:
basic-auth:
oliver:
password: twist
id: 1
ruskin:
password: bond
id: 2
mark:
password: twain
id: 3
In UserDetailsService, I'm not sure how to configure users using application properties dynamically.
public class UserService implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String username) {
String encodedPassword = passwordEncoder.encode( // How to fetch password );
String id = // How to fetch id
return new User(username, encodedPassword, id);
}
}
You can do this in different ways.
For example, let's assume your Basic Authentication properties YAML file, with minimum tweaks for simplicity:
basic-auth:
users:
oliver:
password: twist
id: 1
ruskin:
password: bond
id: 2
mark:
password: twain
id: 3
To make this information accessible to your UserDetailsService service you can use encapsulate this information in form of #ConfigurationProperties, something like the following:
#Component
#ConfigurationProperties(prefix = "basic-auth")
public class BasicAuthProperties {
private Map<String, Credential> users = new HashMap<>();
// Getter & setter
// Convenient method
public Optional<Credential> getCredential(String username) {
Optional.ofNullable(users.get(username));
}
public static class Credential {
private int id;
private String password;
// Getter & setter
}
}
And use this #ConfigurationProperties in your UserDetailsService:
public class UserService implements UserDetailsService {
private final BasicAuthProperties basicAuthProperties;
public UserService(BasicAuthProperties basicAuthProperties) {
this.basicAuthProperties = basicAuthProperties;
}
#Override
public UserDetails loadUserByUsername(String username) {
Optional<BasicAuthProperties.Credential> optCredential = basicAuthProperties.getCredential(username);
if (!optCredential.isPresent()) {
// handle as appropriate, consider at least logging it
return null;
}
BasicAuthProperties.Credential credential = optCredential.get();
String encodedPassword = passwordEncoder.encode(credential.getPassword());
// I assume int, the value is numeric in your YAML file, but modify it
// if necessary
int id = credential.getId();
return new User(username, encodedPassword, id);
}
}
This is my solution for Basic Authentication with yaml from scratch.
1.You need to put this file to the resource folder
appUserList.yml:
constants:
appUserList:
- userId: 1
userName: oliver
password: twist
role: USER
- userId: 2
userName: ruskin
password: bond
role: USER
- userId: 3
userName: mark
password: twain
role: ADMIN
2.You need to make an AppUser class:
public class AppUser {
private String userId;
private String userName;
private String password;
private String role;
public AppUser() {
}
public AppUser(String userId, String userName, String password, String role) {
this.userId = userId;
this.userName = userName;
this.password = password;
this.role = role;
}
//Getter and Setters
}
3.You need to make a YamlPropertySourceFactory class:
public class YamlPropertySourceFactory implements PropertySourceFactory {
#Override
public PropertySource<?> createPropertySource(String name, EncodedResource encodedResource) throws IOException {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(encodedResource.getResource());
Properties properties = factory.getObject();
return new PropertiesPropertySource(encodedResource.getResource().getFilename(), properties);
}
}
4.Now you can use the yaml file and the other two classes to create the AppUserProperties class:
#Component
#PropertySource(value = "classpath:appUserList.yml", factory = YamlPropertySourceFactory.class)
#ConfigurationProperties(prefix = "constants")
public class AppUserProperties {
private List<AppUser> appUserList;
public List<AppUser> getAppUserList() {
return appUserList;
}
public void setAppUserList(List<AppUser> appUserList) {
this.appUserList = appUserList;
}
}
5.Now you need to make a SecurityConf class where you will get the users with this method: appUserProperties.getAppUserList(). If you want use other identifiers for the users, just create the parameters in the AppUser class.
#EnableGlobalMethodSecurity(securedEnabled = true)
#Configuration
public class SecurityConf extends WebSecurityConfigurerAdapter {
#Autowired
AppUserProperties appUserProperties;
#Autowired
public void configureAuth(AuthenticationManagerBuilder auth) throws Exception {
List<AppUser> appUserList = appUserProperties.getAppUserList();
for (AppUser appUser : appUserList) {
auth
.inMemoryAuthentication()
.withUser(appUser.getUserName())
.password(appUser.getPassword())
.roles(appUser.getRole());
}
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/login?logout")
.permitAll();
}
}
These are all the important steps for it to work.
Otherwise, here is the fully working code:
https://github.com/FerencHrutka/Basic-Auth-with-yaml-file
I hope this answers your question.
Related
hi what i trying to achieve is to protect a url that only one role can access to it, when i try add .hasRole("USER"), still the other role can access it. Here is how i do it :
here is my controller :
#RestController
#RequestMapping("/couponapi")
public class CouponController {
#Autowired
CouponRepository couponRepository;
#PostMapping("/coupons")
public Coupon save(#RequestBody Coupon coupon) {
return couponRepository.save(coupon);
}
#GetMapping("/coupons/{code}")
public Coupon findByCode(#PathVariable("code") String code) {
return couponRepository.findByCode(code);
}
#GetMapping("/something")
public Coupon findByCodeX() {
return couponRepository.findByCode("SUPERSALE");
}
}
i want to protect #GetMapping("/something") only for ROLE_ADMIN, here is how my Spring Security Configuration looked like :
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailServiceImpl userDetailService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic();
http.authorizeRequests()
.antMatchers(HttpMethod.GET,"/couponapi/coupons/**").hasRole("USER")
.antMatchers(HttpMethod.POST,"/couponapi/coupons/**").hasRole("USER")
.antMatchers("/couponapi/something").hasRole("ADMIN")
.antMatchers("/**").authenticated()
.and().httpBasic().and().csrf().disable();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
here is my role class :
#Data
#EqualsAndHashCode(of = "id")
#ToString(of = { "id" })
#Entity
public class Roles implements GrantedAuthority {
private static final long serialVersionUID = -7314956574144971210L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToMany(mappedBy = "roles")
private Set<Users> users;
#Override
public String getAuthority() {
return null;
}
}
and here is my service that implements UserDetailsService class :
#Service
public class UserDetailServiceImpl implements UserDetailsService {
#Autowired
UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
Users users = userRepository.findByEmail(s);
if(users == null) {
throw new UsernameNotFoundException("Username Not Found");
}
return new User(users.getEmail(), users.getPassword(), users.getRoles());
}
}
and here is my database role data :
as you can see i have ROLE_USER and ROLE_ADMIN
and here is my joined database
** i just updated my question and i have answer of half of my issue, please read my answer below to see the latest issue
In spring security the most restrictive rules are defined first, therefore your configuration should look like this
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic();
http.authorizeRequests()
.antMatchers(HttpMethod.GET,"/**/something").hasRole("USER")
.antMatchers("/**").authenticated()
.and().httpBasic().and().csrf().disable();
}
i find the culprit here, but not entirely i still missing the functional of HttpMethod one. here is how i fixed the role, in my Role class i do a mistake that i trying to implements the GrantedAuthority class, that the thing that cause this trouble (without HttpMethod issue). and here is how i fixed it
first, delete the impements and turn the role into usual #Entity class :
#Data
#EqualsAndHashCode(of = "id")
#ToString(of = { "id" })
#Entity
public class Roles {
private static final long serialVersionUID = -7314956574144971210L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToMany(mappedBy = "roles")
private Set<Users> users;
}
then at class that implements UserDetailsService, add this code :
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
users.getRoles().forEach(d -> {
grantedAuthorities.add(new SimpleGrantedAuthority(d.getName()));
});
i cannot explain it in details, but i think List only need 1 string, that's string is a role name. then here is the full code :
#Service
public class UserDetailServiceImpl implements UserDetailsService {
#Autowired
UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
Users users = userRepository.findByEmail(s);
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
users.getRoles().forEach(d -> {
grantedAuthorities.add(new SimpleGrantedAuthority(d.getName()));
});
if(users == null) {
throw new UsernameNotFoundException("Username Not Found");
}
return new User(users.getEmail(), users.getPassword(), grantedAuthorities);
}
}
if anyone can find out the fix for this line .antMatchers(HttpMethod.GET,"/**/something").hasRole("USER") which is i want to use HttpMethod to differentiate each method with the same url, if someone have the answer, i will accept your answer. waiting for it
thanks for your good presentation of your problem.
That’s what I underestood, you want to give access only to user with Admin role to this URL /couponapi/something and give access to this URL /couponapi/coupons/** for all authenticated users whatever their roles (ADMIN or USER)
Try use hasAuthority instead of hasRole and delete the first line http.httpBasic() , it worked for me.
So in the WebSecurityConfig.java file:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/couponapi/something").hasAuthority("ROLE_ADMIN")
.antMatchers("/**").authenticated()
.and().httpBasic().and().csrf().disable();
}
Then In the above section of code, you only give access to users with ADMIN authority to access this url /couponapi/something so user with USER authority can’t access it.
And since you have declared a password encoder bean, you can use it with the AuthenticationManagerBuilder
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
I have trouble resolving different encoding types from my LDAP server (which is in SSHA) and the authenticating value of just plain text.
I am using LDAP Repository to retrieve my LDAP server and used AuthenticationManager and my own implementation of `UserDetailService.
The problem is always receive a Bad Credentials exception when i try to authenticate.
As seen on my codes below;
when i request to /auth, i have a plain text username and password. However, i when try to authenticate it via AuthenticationManager, it gives me a Bad Credentials. Now, i know that this has got to do with the UserDetailService.getPassword() and the password i provided in UsernamePasswordAuthenticationToken. Its about the different encryption type. My LDAP has an {SSHA}, also translated to a binary (bytes) and the encoder is Bcrypt and String.
Now my question is how do you properly authenticate a password against the password return by LdapRespository?
UPDATES
2020-04-12 17:09:46.655 INFO 6672 --- [nio-8080-exec-1] s.w.s.SantaRestApiSecurityConfiguration : Raw Password: jdoe123 | Encoded Password: {SSHA}kMgk3gD4prAa/9m4wsPbuAoGhO7UvH2v6+W0Dg==
2020-04-12 17:09:58.110 INFO 6672 --- [nio-8080-exec-1] s.w.s.SantaRestApiSecurityConfiguration : Raw Password: {SSHA}kMgk3gD4prAa/9m4wsPbuAoGhO7UvH2v6+W0Dg== matches Encoded Password: {SSHA}k1Pp3NICHbwuxFFdT7zno/iG/NTILZGL = false
Above logs is during password matching in PasswordEncoder. Based from it. it shows that the Raw Password (inputted by user) has a different hash from the Encoded Password (from ldap server).
I used LdapShaPasswordEncoder.encode to hash user inputted password.
Also, i noticed that everytime i want to authenticate (call http://locahost:xxxx/auth), the user inputted password's hash changes everytime. I think this is normal when hashing
Security Configuration NOTE: I am allowing all REST-API Endpoints for now, i gonna restrict it later
#Configuration
#EnableWebSecurity
public class SantaRestApiSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
AuthService authService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// configure AuthenticationManager so that it knows from where to load
// user for matching credentials
// Use BCryptPasswordEncoder
auth.userDetailsService(authService).passwordEncoder(passwordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// TODO Auto-generated method stub
http.csrf()
.disable()
.authorizeRequests().antMatchers("/*").permitAll();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/*");
}
/*
* i am not sure about this since my LDAP server has
* a SSHA password encrpytion
*
* */
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
LoginController
#RestController
#RequestMapping("/auth")
public class LoginController {
public static Logger logger = LoggerFactory.getLogger(LoginController.class);
#Autowired
private AuthenticationManager authManager;
#PostMapping
public ResponseEntity<String> login(#RequestBody Map<String, String> credentials) {
String username = credentials.get("username");
String password = credentials.get("password");
authManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
return new ResponseEntity<String>("", HttpStatus.OK);
}
}
UserDetailService implementation
#Service
public class AuthService implements UserDetailsService {
public static Logger logger = LoggerFactory.getLogger(AuthService.class);
#Autowired
UserLdapRepository ldapRepo;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserLdap e = ldapRepo.findByUid(username);
logger.info("{}",e.toString());
return new AuthDetails(e.getCn(),
e.getPassword(),
e.getUid(),
e.getMail(),
e.getSn(),
Long.toString( e.getUidNumber()));
}
}
UserDetails implemenation
public class AuthDetails implements UserDetails {
public static Logger logger = LoggerFactory.getLogger(AuthDetails.class);
private String username;
private String password;
private String email;
private String uidNumber;
private String sn;
private String uid;
public AuthDetails(String username, String password, String uid, String email,String sn, String uidNumber) {
this.uid = uid;
this.password = password;
this.username = username;
this.email = email;
this.sn = sn;
this.uidNumber = uidNumber;
}
/*
* Here i am not sure how to implement this one to satisfy AuthenticationManager
*
*
**/
#Override
public String getPassword() {
return this.password;
}
#Override
public String getUsername() {
// TODO Auto-generated method stub
return this.username;
}
// some method to implement
}
You are using BCryptPasswordEncoder but in LDAP is using the SSHA encryption.
You need to modify the method passwordEncoder in class SantaRestApiSecurityConfiguration
#Bean
public PasswordEncoder passwordEncoder() {
return new LdapShaPasswordEncoder();
}
public Class UserLdap {
#Attribute(name = "userPassword", type = Type.BINARY)
private byte[] password;
}
and change the method loadUserByUsername
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserLdap e = ldapRepo.findByUid(username);
logger.info("{}",e.toString());
String password = "{SSHA}" + new String(e.getPassword());
return new AuthDetails(e.getCn(),
password,
e.getUid(),
e.getMail(),
e.getSn(),
Long.toString( e.getUidNumber()));
}
After configuring a custom user details service that uses an embedded H2 database to retrieve user credentials, I keep receiving 401 Unauthorized errors when completing a post command even though the username and password are available. I don't know if this makes any difference, but the console still prints out the generated security password (although the auto generated credentials return the same 401 error). Please take a look at my code below and let me know any suggestions or fixes available.
User model...
#Entity
public class ApplicationUser {
#Id
private Long id;
private String username, password, role;
public ApplicationUser(String username, String role, String password) {
this.username=username;
this.password=password;
this.role=role;
}
public ApplicationUser() {
}
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 String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
#Override
public String toString() {
return "ApplicationUser{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", role='" + role + '\'' +
'}';
}
}
User repository class...
#Repository
public class AppUserRepository {
#Autowired
JdbcTemplate jdbcTemplate;
static class ApplicationUserRowMapper implements RowMapper<ApplicationUser> {
#Override
public ApplicationUser mapRow(ResultSet rs, int rowNum) throws SQLException {
ApplicationUser applicationUser = new ApplicationUser();
applicationUser.setUsername(rs.getString("username"));
applicationUser.setPassword(rs.getString("password"));
applicationUser.setRole(rs.getString("userrole"));
return applicationUser;
}
}
public List<ApplicationUser> findAll() {
return jdbcTemplate.query("select u.username, u.password, ur.userrole from ApplicationUsers u, ApplicationUsers_Role ur where u.username = ur.username",
new ApplicationUserRowMapper());
}
public ApplicationUser findByUsername(String username) {
return jdbcTemplate.queryForObject("select u.username, u.password, ur.userrole from ApplicationUsers u, ApplicationUsers_Role ur where u.username = ? and u.username = ur.username",
new Object[] {username}, new ApplicationUserRowMapper());
}
}
Security config class...
#Configuration
#EnableWebSecurity
#ComponentScan(basePackages = "security_package")
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
CustomUserDetailsService customUserDetailsService;
#Autowired
CustomAuthenticationProvider customAuthenticationProvider;
#Bean
public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
#Autowired
public void configureGlobal (AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider);
auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().antMatchers("/login").permitAll().anyRequest().authenticated()
.and()
.formLogin().loginPage("/login").usernameParameter("username").passwordParameter("password");
}
}
Custom authentication provider...
#Service
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
AppUserRepository userRepository;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String name = authentication.getName();
String password = authentication.getCredentials().toString();
ApplicationUser user = userRepository.findByUsername(name);
if(user == null) {
throw new BadCredentialsException("Authentication failed.");
}
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
grantedAuthorities.add(new SimpleGrantedAuthority(user.getRole()));
return new UsernamePasswordAuthenticationToken(name, password, grantedAuthorities);
}
#Override
public boolean supports(Class<?> aClass) {
return aClass.equals(UsernamePasswordAuthenticationToken.class);
}
}
Main app class...
#SpringBootApplication(exclude={
HazelcastAutoConfiguration.class,
DataSourceAutoConfiguration.class,
SecurityAutoConfiguration.class
})
#EnableScheduling
#Slf4j
public class AwsorchestratorApplication implements CommandLineRunner{
#Override
public void run(String... arg0) throws Exception {
if (arg0.length > 0 && arg0[0].equals("exitcode")) {
throw new ExitException();
}
}
public static void main(String[] args) throws Exception {
if ( System.getProperty("spring.profiles.active") == null )
{
System.setProperty("spring.profiles.active","local");
}
new SpringApplication(AwsorchestratorApplication.class).run(args);
}
class ExitException extends RuntimeException implements ExitCodeGenerator {
private static final long serialVersionUID = 1L;
#Override
public int getExitCode() {
return 10;
}
}
}
UPDATE ONE::...
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private AppUserRepository userRepo;
#Override
public UserDetails loadUserByUsername(String username) {
ApplicationUser user = userRepo.findByUsername(username);
if(user == null) {
throw new UsernameNotFoundException("User '" + username + "' not found.");
}
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(user.getRole());
return new User(user.getUsername(), user.getPassword(), Collections.singletonList(grantedAuthority));
}
}
I was able to correct this issue by removing my custom authentication class (although I don't think this was actually part of the problem), and editing my web security config to match the below.
#Configuration
#EnableWebSecurity
#ComponentScan(basePackages = package_location)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public CustomUserDetailsService detailsService;
#Bean
public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
#Autowired
public void configureGlobal (AuthenticationManagerBuilder auth) throws Exception {
//the password encoder here is only to support a mix of encoding - that change can be removed
auth.userDetailsService(detailsService).passwordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.headers()
.frameOptions()
.disable()
.and()
.authorizeRequests()
.antMatchers("/awsorchestrator/**")
.hasAnyRole("ADMIN", "USER")
.and()
.formLogin()
.usernameParameter("username")
.passwordParameter("password")
.and()
.csrf()
.disable();
}
}
The important part that directly correlated to my problem was the passwordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder()) portion. I also had to change the password stored in the database to be prefixed with {bcrypt} and now I am able to login with no issues.
Thanks R.G for the tips.
I'm implementing a project using Spring security oauth2, everything works perfectly, now I want to start digging deeper beyond the basics. I want to check if the user making the request is the actual user owner of the resource, the end result would be for example:
/private/users/{uuid}/clients returns all clients for the specified user.
So my controller now looks like this:
#RestController
public class HomeController {
#Autowired
private UserService userService;
#GetMapping(value = "/")
public String index() {
return "Hello world";
}
#GetMapping(value = "/private")
public String privateTest(Principal principal) {
User user = userService.get(principal.getName());
return user.getUuid();
}
}
EDIT: The full security code (working) for a better explanation.
ResourceServerConfig
#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("/private/**").authenticated();
}
}
CustomUserDetails with getters and setters off course
public class CustomUserDetails implements UserDetails {
private Collection<? extends GrantedAuthority> authorities;
private String password;
private String username;
private String uuid;
public CustomUserDetails(User user) {
this.username = user.getUsername();
this.password = user.getPassword();
this.uuid = user.getUuid();
this.authorities = translate(user.getRoles());
}
}
AuthorizationServerConfig
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("my-trusted-client")
.authorizedGrantTypes("client_credentials", "password")
.authorities("ROLE_CLIENT","ROLE_TRUSTED_CLIENT").scopes("read","write","trust")
.resourceIds("oauth2-resource").accessTokenValiditySeconds(5000).secret("secret");
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.checkTokenAccess("isAuthenticated()");
}
}
Main
#SpringBootApplication
public class DummyOauthApplication {
#Autowired
private PasswordEncoder passwordEncoder;
public static void main(String[] args) {
SpringApplication.run(DummyOauthApplication.class, args);
}
#Autowired
public void authenticationManager(AuthenticationManagerBuilder builder, UserRepository repository, UserService service) throws Exception {
//Setup a default user if db is empty
if (repository.count() == 0) {
service.save(new User("user", "password", UUID.randomUUID().toString(), Arrays.asList(new Role("USER"), new Role("ACTUATOR"))));
}
builder.userDetailsService(userDetailsService(repository)).passwordEncoder(passwordEncoder);
}
private UserDetailsService userDetailsService(final UserRepository repository) {
return username -> new CustomUserDetails(repository.findByUsername(username));
}
}
So, using the way I've implemented. I can get the actual user but it implies a database query every time an endpoint is called. Getting the user and match with the user uuid.
I want to find another way that I can get the user and then compare if the uuid = user.getUuid()
Thanks in advance.
After some time and a lot of mistakes, I've managed to find a solution that I leave here. The CustomUserDetails can be seen in the question and from there you can easily get the uuid and match with the requested one.
public static CustomUserDetails getCurrentUser() {
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
if (authentication != null) {
if (authentication.getPrincipal() instanceof CustomUserDetails) {
return (CustomUserDetails) authentication.getPrincipal();
}
}
throw new IllegalStateException("User not found!");
}
EDIT: if you want to return the user you do something like this
public class CustomUserDetails implements UserDetails {
private Collection<? extends GrantedAuthority> authorities;
private String password;
private String username;
private User user;
public CustomUserDetails(User user) {
this.username = user.getUsername();
this.password = user.getPassword();
this.user = user;
this.authorities = translate(user.getRoles());
}
}
And then in a Utils or something,
public static User getCurrentUser() {
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
if (authentication != null) {
if (authentication.getPrincipal() instanceof CustomUserDetails) {
CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
return userDetails.getUser();
}
}
throw new IllegalStateException("User not found!");
}
Thanks for all the effort.
Perhaps you could implement a custom AuthenticationProvider and store user details as Principal
Spring Security Authentication Provider
I'm trying to add bcrypt to a spring app of mine. Authentication works just fine without. But when I try to encode using bcrypt I get "Reason: Bad credentials" when trying to login.
My user model looks as follows.
#Entity
#Table(name="users") // user is a reserved word in postgresql
public class User extends BaseEntity {
private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
...
#Column(nullable=false)
private String password;
...
public String getPassword() {
return password;
}
public void setPassword(String password) {
String hashedPassword = passwordEncoder.encode(password);
this.password = hashedPassword;
}
...
}
My SecurityConfig looks as follows.
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService userDetailsService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
private BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
...
}
Do the above seem right? Do I need to do more than what I've already done?
My bad for not posting enough code. Naturally my user model didn't tell the entire story. I also have a class called SecurityUser which I've posted below. Due to the copy constructor the password gets hashed twice.
public class SecurityUser extends User implements UserDetails {
private static final long serialVersionUID = 867280338717707274L;
public SecurityUser(User user) {
if(user != null)
{
this.setId(user.getId());
this.setName(user.getName());
this.setEmail(user.getEmail());
this.setPassword(user.getPassword());
this.setRoles(user.getRoles());
}
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
Set<Role> userRoles = this.getRoles();
if(userRoles != null)
{
for (Role role : userRoles) {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role.getName());
authorities.add(authority);
}
}
return authorities;
}
...
}
I've made my passwordEncoder method public and promoted it to a bean so I can autowire it into my UserService which is shown below. That way I only have to change encoder in one place if I ever decide to do so.
#Service
public class UserService {
#Autowired
private UserRepository userRepository;
#Autowired
private PasswordEncoder passwordEncoder;
public User create(User user) {
String hashedPassword = passwordEncoder.encode(user.getPassword());
user.setPassword(hashedPassword);
return userRepository.save(user);
}
...
}
Here is how I would set it up.
User Table has 4 properties (amongst others)
id (auto increment)
username (or email_address) field
password field.
enabled (value will be either 1 or 0)
Role table (3 properties)
id (auto increment)
user_id (user table foreign key)
authority;
Create Java Entities for the two tables.
Spring Security Configuration Class looks like:
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
String usrsByUnameQry = "SELECT u.email_address, u.password, u.enabled FROM user u WHERE u.email_address=?";
3String authByUnameQry = "SELECT u.email_address, r.authority FROM user u, role r WHERE u.id=r.user_id AND u.email_address=?";
auth
.eraseCredentials(false)
.jdbcAuthentication()
.dataSource(dataSource)
.passwordEncoder(passwordEncoder())
.usersByUsernameQuery(usrsByUnameQry)
.authoritiesByUsernameQuery(authByUnameQry);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.usernameParameter("username") //username property defined in html form
.passwordParameter("password") //password property defined in html form
// url that holds login form
.loginPage("/signin")
.loginProcessingUrl("/signin/authenticate")
.failureUrl("/loginfail")
// Grant access to users to the login page
.permitAll()
.and()
.logout()
.logoutUrl("/signout")
.deleteCookies("JSESSIONID")
.logoutSuccessUrl("/signin")
.and()
.authorizeRequests()
.antMatchers("/foo/**").permitAll()//Grant access to all (no auth reuired)
.antMatchers("/").hasAnyAuthority("ROLE_USER","ROLE_ADMIN") //Grant access to only users with role "ROLE_USER" or "ROLE_ADMIN"
}
#Bean(name = "authenticationManager")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
#Bean
public TextEncryptor textEncryptor(){
return Encryptors.noOpText();
}