Manual authentication throw CustomAuthenticationProvider (Spring MVC) - spring

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/

Related

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

SecurityContextHolder returns correct user's value but Authentication object returns null

I've used 2 login pages, 1 for users and another for admin. But I have stored admin information in memory but have fetched user's information from the database. The problem here is, when I want to use Authentication object it returns null. But SecurityContextHolder gives me the perfect value. I want to set this Authentication value globally, so that my every method can have it.
Here is my SecurityConfig class
// admin login class
#Configuration
#Order(1)
public class AdminAuthorization extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/admin/**").authorizeRequests().anyRequest().hasRole("ADMIN").and().formLogin()
.loginPage("/adminLogin").loginProcessingUrl("/admin/dashboard").and().csrf().disable();
}
// for authentication
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("admin").password(encoder().encode("admin")).roles("ADMIN");
}
}
// Publisher login class
#Configuration
#Order(2)
public class PublisherAuthorization extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) {
try {
http.authorizeRequests()
.antMatchers("/publisher/**").hasRole("PUBLISHER")
.and().formLogin().loginPage("/login")
.loginProcessingUrl("/login").successForwardUrl("/publisher/welcome")
.failureUrl("/login?error").usernameParameter("username").passwordParameter("password");
} catch (Exception e) {
e.printStackTrace();
}
}
// for authentication
#Autowired
public void configure(AuthenticationManagerBuilder auth) {
try {
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery("select username, password, active" + " from publisher where username=?")
.passwordEncoder(encoder())
.authoritiesByUsernameQuery("select username, authority " + "from authorities where username=?");
} catch (Exception e) {
e.printStackTrace();
}
}
}
#Bean
public static PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
HomeController
#PostMapping(value = { "/welcome", "/welcome/{QuestionPageNumber}/{ArticlePageNumber}" })
public ModelAndView page(Authentication auth, #PathVariable Optional<Integer> QuestionPageNumber,
#PathVariable Optional<Integer> ArticlePageNumber) {
System.out.println(auth==null); //returns true
//but
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
System.out.println(authentication.getName()); //returns correct user's information
Now, the problem is, I don't to use this code
SecurityContextHolder.getContext().getAuthentication();
on every line.(I don't know the reason why!!)..
I'm unable to collect my publisher's information. Admin is working fine.
Try this code instead of your in memory auth implementation:
import org.springframework.security.core.userdetails.User;
String username = "ADMIN";
String encodedPassword = new BCryptPasswordEncoder().encode("admin");
List<SimpleGrantedAuthority> authList = Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN"));
User user = new User(username, encodedPassword, authList);
auth.inMemoryAuthentication().withUser(user);

Add additional role to Keycloak authentication from outer source

I wanna authenticate users via Keycloak, but I need to add additional roles to Authentication object, that is using by Spring Security. Adding roles are saved in Postgres database.
I tried to override configureGlobal with custom AuthenticationProvider, but it didn't work.
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
ApplicationAuthenticationProvider provider = new ApplicationAuthenticationProvider();
provider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(provider);
}
#Component
public class ApplicationAuthenticationProvider extends KeycloakAuthenticationProvider {
#Autowired
private UserService userService;
private GrantedAuthoritiesMapper grantedAuthoritiesMapper;
public void setGrantedAuthoritiesMapper(GrantedAuthoritiesMapper grantedAuthoritiesMapper) {
this.grantedAuthoritiesMapper = grantedAuthoritiesMapper;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
KeycloakAuthenticationToken token = (KeycloakAuthenticationToken) authentication;
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
String username = ((KeycloakAuthenticationToken) authentication)
.getAccount().getKeycloakSecurityContext().getToken().getPreferredUsername();
List<Role> roles = userService.findRoles(username);
for (Role role : roles) {
grantedAuthorities.add(new KeycloakRole(role.toString()));
}
return new KeycloakAuthenticationToken(token.getAccount(), token.isInteractive(), mapAuthorities(grantedAuthorities));
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
private Collection<? extends GrantedAuthority> mapAuthorities(
Collection<? extends GrantedAuthority> authorities) {
return grantedAuthoritiesMapper != null
? grantedAuthoritiesMapper.mapAuthorities(authorities)
: authorities;
}
}
Tried to add additional filter, but i'm not sure in correct configuration.
#Bean
#Override
protected KeycloakAuthenticationProcessingFilter keycloakAuthenticationProcessingFilter() throws Exception {
RequestMatcher requestMatcher =
new OrRequestMatcher(
new AntPathRequestMatcher("/api/login"),
new QueryParamPresenceRequestMatcher(OAuth2Constants.ACCESS_TOKEN),
// We're providing our own authorization header matcher
new IgnoreKeycloakProcessingFilterRequestMatcher()
);
return new KeycloakAuthenticationProcessingFilter(authenticationManagerBean(), requestMatcher);
}
// Matches request with Authorization header which value doesn't start with "Basic " prefix
private class IgnoreKeycloakProcessingFilterRequestMatcher implements RequestMatcher {
IgnoreKeycloakProcessingFilterRequestMatcher() {
}
public boolean matches(HttpServletRequest request) {
String authorizationHeaderValue = request.getHeader("Authorization");
return authorizationHeaderValue != null && !authorizationHeaderValue.startsWith("Basic ");
}
}
Now I use Keycloak only for login/password. Roles and permissions now saved in local DB.

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;
}

How to add new user to Spring Security at runtime

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)
);

Resources