How to add new user to Spring Security at runtime - spring

I save users in a DB table via Hibernate and I am using Spring Security to authenticate:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.*;
import org.springframework.security.config.annotation.authentication.builders.*;
import org.springframework.security.config.annotation.web.configuration.*;
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
}
And this works perfectly, but there is a point - user is loaded during server start. I need to write method RegisterUser(User user) that add new user to Spring Security in runtime. This method should focus only on this task. I dont know how to start to implement this feature so thanks for any advices! ;)
Ofc User have fields like login, password, role string etc etc...
Please do not post solutions with Spring MVC. This system is RESTful app using Spring Web Boost and Spring Security Boost in version 4.0.x

You probably want to store your users in a database and not in memory, if they are registering :)
Create the authorities for the user
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
Instantiate the user (with a class implementing UserDetails)
UserDetails user = new User("user#example.com", passwordEncoder.encode("s3cr3t"), authorities);
Save the user somewhere useful. The JdbcUserDetailsManager can save a user to a database easily.
userDetailsManager.createUser(user);
Create a UsernamePasswordAuthenticationToken
Authentication authentication = new UsernamePasswordAuthenticationToken(user, null, authorities);
Add the Authentication to the SecurityContext
SecurityContextHolder.getContext().setAuthentication(authentication);

You can use Spring Data JPA for user creation.
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
usage:
User user = new User();
userRepository.save(user);
How to authenticate above user:
Create custom AuthenticationProvider, select user data from your DB and authenticate:
#Component
public class MyAuthenticationProvider implements AuthenticationProvider {
#Autowired
private UserRepository userRepository;
#Override
public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
final UsernamePasswordAuthenticationToken upAuth = (UsernamePasswordAuthenticationToken) authentication;
final String name = (String) authentication.getPrincipal();
final String password = (String) upAuth.getCredentials();
final String storedPassword = userRepository.findByName(name).map(User::getPassword)
.orElseThrow(() -> new BadCredentialsException("illegal id or passowrd"));
if (Objects.equals(password, "") || !Objects.equals(password, storedPassword)) {
throw new BadCredentialsException("illegal id or passowrd");
}
final Object principal = authentication.getPrincipal();
final UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
principal, authentication.getCredentials(),
Collections.emptyList());
result.setDetails(authentication.getDetails());
return result;
}
...
Configure with WebSecurityConfigurerAdapter for using above AuthenticationProvider:
#EnableWebSecurity
public class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Autowired
private MyAuthenticationProvider authProvider;
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
http.authenticationProvider(authProvider);
}
}
refs:
Spring Security Architecture
complete code sample

use this code to add authority to current user:
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_NEWUSERROLE');
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken(
SecurityContextHolder.getContext().getAuthentication().getPrincipal(),
SecurityContextHolder.getContext().getAuthentication().getCredentials(),
authorities)
);

Related

Configuring both http basic and Form based authentication in latest Spring security 6.0

I'm trying to configure REST and Form based authentication in new Spring boot and spring secuirty latest. I went through following post Combining basic authentication and form login for the same REST Api and new spring configuration using SecurityFilterChain and created the following. As I learnt that WebSecurityConfigurerAdapter method in the above post is deprecated.
#Configuration
#Order(2)
#EnableWebSecurity
public class RESTBasedConfigurationAdapter {
#Autowired
private AuthenticationProvider authenticationProvider;
#Bean
public BCryptPasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
#Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**")
.authorizeRequests().anyRequest().hasAnyRole(...)
.and().httpBasic()
.authenticationEntryPoint(authenticationEntryPoint());
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
return http.build();
}
}
and
#Configuration
#Order(Ordered.HIGHEST_PRECEDENCE)
#EnableWebSecurity
public class FormBasedConfigurationAdapter {
#Autowired
private AuthenticationProvider authenticationProvider;
#Autowired
private AuthenticationSuccessHandler authenticationSuccessHandler;
#Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers(...)
.permitAll().anyRequest().authenticated()
.and().formLogin().loginPage("/login").successHandler(authenticationSuccessHandler)
.permitAll().and().logout().permitAll();
return http.build();
}
}
But the configure method FormBasedConfigurationAdapter's is never called. Please explain how to configure so that both http Basic and Form based Authentication can be done.
As far I under I want two flows.
One flow REST which uses the following AuthenticationProvider for logging STATELESS
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
#Lazy
private BCryptPasswordEncoder passwordEncoder;
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
String userName = authentication.getName();
String password = authentication.getCredentials().toString();
UserDetails userDetails = userDetailsService.loadUserByUsername(userName);
if (passwordEncoder.matches(password, userDetails.getPassword())) {
return new UsernamePasswordAuthenticationToken(userName, password, userDetails.getAuthorities());
} else {
throw new BadCredentialsException(" Bad Credentials ");
}
}
#Override
public boolean supports(Class<?> authenticationType) {
return authenticationType.equals(UsernamePasswordAuthenticationToken.class);
}
}
and for other FormBased authentication, I would like to go through controller as below.
#PostMapping("/login")
public String login(#ModelAttribute("loginForm") LoginForm loginForm,
BindingResult bindingResult,
Model model) {
loginValidator.validate(loginForm, bindingResult);
securityService.login(loginForm.getUserName(), loginForm.getPasswd());
if (bindingResult.hasErrors()) {
return "login";
}
return "...";
}
to facilitate Validation for Authenticate using Service as below.
#Override
public void login(String username, String password) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
if (passwordEncoder.matches(password, userDetails.getPassword())) {
if (usernamePasswordAuthenticationToken.isAuthenticated()) {
SecurityContextHolder.getContext()
.setAuthentication(usernamePasswordAuthenticationToken);
logger.debug(String.format("Auto login %s successfully!", username));
}
} else {
throw new BadCredentialsException(" Bad Credentials ");
}
}
Please explain how to achieve this. I also tried doing both HttpSecurity mapping in the same class but it is not working due to various reasons.

Spring boot jwt authentication only

I'm trying to implement spring boot authentication without any authorization. So simple signin,signup and profile apis where the signin and signup api has been permitted for all and profile is only for authenticated users.
Most tutorials focus on RBAC and authentication together while I just want to focus on the authentication part.
I have already created the basic structure with the help of scattered tutorials.
My AuthController:
private final AuthService authService;
#PostMapping("/signin")
public ResponseEntity<?> signin(#RequestBody SigninRequest signinRequest) {
String email = signinRequest.getEmail();
log.info("email : {}", email);
String password = signinRequest.getPassword();
User user = authService.signin(email, password);
return ResponseEntity.ok().body(user);
}
#PostMapping("/signup")
public ResponseEntity<User> signup(#RequestBody User user){
URI uri = URI.create(ServletUriComponentsBuilder.fromCurrentContextPath().path("/api/v1/auth/signup").toUriString());
return ResponseEntity.created(uri).body(authService.signup(user));
}
My AuthServiceImpl:
public class AuthServiceImpl implements AuthService, UserDetailsService {
private final AuthRepository authRepository;
private final PasswordEncoder passwordEncoder;
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
User user = authRepository.findByEmail(email);
if (user == null) {
throw new UsernameNotFoundException(email);
} else {
log.info("User found: {}", user);
}
return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(), new ArrayList<>());
}
#Override
public User signup(User user) {
String encodedPassword = passwordEncoder.encode(user.getPassword());
user.setPassword(encodedPassword);
return authRepository.save(user);
}
#Override
public User signin(String email, String password) {
// handle login ??
return user;
}
}
Everything was going fine until every tutorial hits the point where they send authorities or roles back to the client side. I wish to make this application authentication only.
And my security config as of now:
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsService userDetailsService;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/api/v1/auth/signin").permitAll()
.antMatchers("/api/v1/auth/signup").permitAll()
.antMatchers("/api/v1/auth/profile").authenticated();
http.httpBasic();
}
}
My plan is simple:
User can register
User Can login
Upon login a access token and refresh token will be issued like usual
A Dockerised role based access system and advanced modifiable user-api access system
Spring Security, JWT, DB Based Authentication upon APIs
https://github.com/abhitkumardas/rbac

refresh_token grant_type error: UserDetailsService is required. But I dont want to specify one

I'm trying to create an Oauth authentication/authorization server using spring boot and dependencies
* spring-security-oauth2-autoconfigure
* nimbus-jose-jwt
and I'm following docs.spring.io/spring-security-oauth2-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-security-oauth2-authorization-server
The issue is that I don't want to specify a UserDetailsService since the information about the user account is in another service that doesn't expose passwords. That service just has an API in which input is user/pass and output is user info (if the user exists/credentials are correct).
So my code/configuration is a little deviated from the documentation.
#EnableAuthorizationServer
#Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
//injections
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.tokenStore(jwtTokenStore)
.accessTokenConverter(accessTokenConverter)
.authenticationManager(authenticationManager);
}
}
and
#EnableWebSecurity
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//injections
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) {
authenticationManagerBuilder.authenticationProvider(travelerAuthenticationProvider); //my custom // authentication provider that calls the other service for checking credentials
}
}
and
#Component
public class TravelerAuthenticationProvider implements AuthenticationProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(TravelerAuthenticationProvider.class);
private OrderTravelerProfileClient travelerProfileClient;
public TravelerAuthenticationProvider(OrderTravelerProfileClient travelerProfileClient) {
this.travelerProfileClient = travelerProfileClient;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication.getName() == null || (authentication.getCredentials().toString().isEmpty())) {
return null;
}
var username = authentication.getName();
var password = authentication.getCredentials().toString();
try {
travelerProfileClient.authenticate(username, password);
} catch (Exception e) {
LOGGER.error("checking traveler {} credentials failed", username, e);
throw new BadCredentialsException("wrong traveler credentials");
}
var authorities = Set.of(new SimpleGrantedAuthority("traveler"));
var updatedAuthentication = new UsernamePasswordAuthenticationToken(username, password, authorities);
return updatedAuthentication;
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
Everything related to client_credentials and password flow works but when I try to use refresh_token flow, it complains that UserDetailsService is required. How should I solve the issue without defining a UserDetailsService and just relaying on my custom authentication provider?
UPDATE:
apparently refresh_token flow has a recheck for authentication (credentials) which needs another authentication provider for type PreAuthenticatedAuthenticationToken.class.
So I created a new auth provider like this:
#Component
public class TravelerRefreshTokenBasedAuthenticationProvider implements AuthenticationProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(TravelerRefreshTokenBasedAuthenticationProvider.class);
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
var currentAuthentication = (PreAuthenticatedAuthenticationToken) authentication;
//.....
return updatedAuthentication;
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(PreAuthenticatedAuthenticationToken.class);
}
}
and update my security configs to:
#EnableWebSecurity
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//injections
//this bean will be more configured by the method below and it will be used by spring boot
//for authenticating requests. Its kind of an equivalent to userDetailsService
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) {
authenticationManagerBuilder.authenticationProvider(travelerUserPassBasedAuthenticationProvider);
authenticationManagerBuilder.authenticationProvider(travelerRefreshTokenBasedAuthenticationProvider);
}
}
the issue is spring doesn't recognize my auth providers in refresh_token flow and tries to use a default one. And the default one is trying to use a UserDetailsService that doesn't exist.
I also feel that I don't need to create another provider and I can reuse the previous one. Because the check for which spring is failing to use my custom provider is a check against user/pass; which I was doing in my previous auth provider.
so all in all, until now, I feel I have to introduce my custom provider to spring differently for refresh_token flow comparing to password flow
Your AuthenticationProvider implementation only supports UsernamePasswordAuthenticationToken, which is used for username/password authentication, while the refresh_token flow tries to renew authentication using PreAuthenticatedAuthenticationToken (see DefaultTokenServices.java).
So you need to create another AuthenticationProvider for PreAuthenticatedAuthenticationToken and add it to AuthenticationManagerBuilder.
Update:
I've found that AuthorizationServerEndpointsConfigurer creates a new instance of DefaultTokenServices, if none is assigned, which in turn creates a new instance of PreAuthenticatedAuthenticationProvider and does not use the provided AuthenticationManager. To avoid this, you can create your own instance of DefaultTokenServices and pass it to AuthorizationServerEndpointsConfigurer:
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.tokenStore(jwtTokenStore)
.accessTokenConverter(accessTokenConverter)
.tokenEnhancer(accessTokenConverter)
.authenticationManager(authenticationManager)
.tokenServices(createTokenServices(endpoints, authenticationManager));
}
private DefaultTokenServices createTokenServices(AuthorizationServerEndpointsConfigurer endpoints, AuthenticationManager authenticationManager) {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setSupportRefreshToken(true);
tokenServices.setTokenStore(endpoints.getTokenStore());
tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
tokenServices.setAuthenticationManager(authenticationManager);
return tokenServices;
}

Spring Security REST Login

I got question about login with REST API with Spring Security. As far as login with default login window provided by Spring Security is working and it is authenticating with Database, I have no idea how to make my own login. I know how to substitute the form for my own, but where should I send the data? Should I POST it for some address? I made basic form with username and password.
Try this one, it might help you... at least to understand what you are missing.
This code is not guarantee to be worked 100%, some part is intentionally missed (error handling and it's format, loading user, some checks, Session API).
The basic idea is you must to register a filter (react on all secured request for authentication process) and a provider that later on will be able to load authonticated user and create for you security context (e.g. you know each request is handled per thread and this user can be obtained by SecurityContextHolder/ThreadLocal).
And you need to create a separate controller to handle the initial case for creating a user session aka login/Authorization. Response of this API must to contain some session's GUID to use it as value of header later on: Authentication: Bearer <value>
some spec: https://www.rfc-editor.org/rfc/rfc6750
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)//optional
#Import(RestSecurityConfig.TokenAuthenticationProvider.class)// one of the way to create spring bean
public class RestSecurityConfig extends WebSecurityConfigurerAdapter {
private static final RequestMatcher PUBLIC_URLS = new OrRequestMatcher(
new AntPathRequestMatcher("/actuator/*"),
new AntPathRequestMatcher("/some_api_to_login", POST), // this must be public
);
private static final RequestMatcher PROTECTED_URLS = new NegatedRequestMatcher(PUBLIC_URLS);
// better to move it out as a separate class
public static class TokenAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
#Override
public boolean supports(Class<?> authentication) {
return MyAuthenticationToken.class.isAssignableFrom(authentication);
}
#Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
}
#Override
protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
return null; // service/dao.loadUser
}
}
public static class TokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public TokenAuthenticationFilter(RequestMatcher requiresAuthenticationRequestMatcher) {
super(requiresAuthenticationRequestMatcher);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
Authentication auth = new MyAuthenticationToken(request.getHeader("Authentication"));
return getAuthenticationManager().authenticate(auth);
}
}
#Autowired
TokenAuthenticationProvider authenticationProvider;
#Override
protected void configure(final AuthenticationManagerBuilder auth) {
auth.authenticationProvider(authenticationProvider);
}
#Override
public void configure(final WebSecurity web) {
web.ignoring().requestMatchers(PUBLIC_URLS);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// maybe some of the tuning you might not need
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.exceptionHandling()
.defaultAuthenticationEntryPointFor(new Http403ForbiddenEntryPoint(), PROTECTED_URLS).and()
.authorizeRequests().anyRequest().authenticated().and()
.cors().and()
.anonymous().disable()
.rememberMe().disable()
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.logout().disable();
// it's important
http.addFilterBefore(tokenAuthenticationFilter(), AnonymousAuthenticationFilter.class);
}
#Bean
AbstractAuthenticationProcessingFilter tokenAuthenticationFilter() throws Exception {
final AbstractAuthenticationProcessingFilter filter = new TokenAuthenticationFilter(PROTECTED_URLS);
filter.setAuthenticationManager(authenticationManager());
filter.setAuthenticationSuccessHandler(successHandler());
// maybe error handling to provide some custom response?
return filter;
}
// it's critically important to register your filter properly in spring context
/** Disable Spring boot automatic filter registration. */
#Bean
FilterRegistrationBean disableRegistrationForAuthenticationFilter(final TokenAuthenticationFilter filter) {
final FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}
// this one also is critically important to avoid redirection
#Bean
SimpleUrlAuthenticationSuccessHandler successHandler() {
final SimpleUrlAuthenticationSuccessHandler successHandler = new SimpleUrlAuthenticationSuccessHandler();
successHandler.setRedirectStrategy(new NoRedirectStrategy());
return successHandler;
}
}
You can store usernames and passwords in database, which you can use to login users. You create your own class which extends WebSecurityConfigurerAdapter and override methods which you need to modify:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
DataSource dataSource;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.jdbcAuthentication()
.dataSource(dataSource)
}
}
But be vary of Spring Security default database query when searching for usernames and passwords so you can create database schema which will be good:
public static final String DEF_USERS_BY_USERNAME_QUERY =
"select username,password,enabled " +
"from users " +
"where username = ?";
public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY =
"select username,authority " +
"from authorities " +
"where username = ?";
public static final String DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY =
"select g.id, g.group_name, ga.authority " +
"from groups g, group_members gm, group_authorities ga " +
"where gm.username = ? " +
"and g.id = ga.group_id " +
"and g.id = gm.group_id";
But you can also use Spring methods to specify your own query to database:
auth
.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery(
"select username, password, enabled from Users " +
"where username=?")
You should POST your data to some service you created which will store user and pass to a database.

Manual authentication throw CustomAuthenticationProvider (Spring MVC)

I try to manual login with username and password with Spring MVC
I have CustomSessionAuthenticationProvider:
public class CustomSessionAuthenticationStrategy implements AuthenticationProvider {
#Autowired
private UserService userService;
#Override
public Authentication authenticate(Authentication authentication) {
String username = authentication.getName();
String password = (String) authentication.getCredentials();
User user = userService.loadUserByUsername(username);
if (user == null) {
throw new BadCredentialsException("Username not found.");
}
if (!password.equals(user.getPassword())) {
throw new BadCredentialsException("Wrong password.");
}
Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
return new UsernamePasswordAuthenticationToken(user, password, authorities);
}
#Override
public boolean supports(Class<?> arg0) {
return true;
}
}
I included it in Security Config :
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
I don't understand how manually authenticate, if i have in controller username and password and get sessionId and token from it.
Method authenticate in input need authentication, but i need login and password.
You do not need a controller for manual authentication, spring security registers a controller at /login for the post method automatically and does the authentication for you, if you have configured it correctly. Please have a look at this blog post for an example or the spring security manual: http://kielczewski.eu/2014/12/spring-boot-security-application/

Resources