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

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

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.

Configuring multiple authentication providers in conjunction with oauth2+jwt

I am trying to configure my Spring Boot 2, OAuth2 with JWT Authorization Server, which needs to do the following:
take a username/password, build a CustomUserDetails object based on db data and salesforce data, and return a JWT token if authentication passes (this works)
take a refresh token and return a new JWT refresh and access token (this works)
(NEW) take a refresh token, check against the db for a stored token ID before returning a new JWT access + refresh token (this is the trouble spot) The point of this is to ensure only one device is logged in with the user's credentials at a time.
In order to do #3, I am trying to customize the PreAuthenticatedAuthenticationProvider by giving it a custom UserDetailsService, and the AuthenticationManagerBuilder bean needs to be passed both the customized PreAuthenticatedAuthenticationProvider and the DaoAuthenticationProvider.
Assuming I am going in the right direction with that, here is my configuration code:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsService userDetailsService;
#Autowired
PasswordEncoder passwordEncoder;
//implements AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken>
#Autowired
CustomPreauthenticatedUserDetailsService customPreauthenticatedUserDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/actuator/**").permitAll()
.antMatchers("/swagger-ui**","/webjars/**","/swagger-resources/**", "/v2/**").permitAll()
.antMatchers("/oauth/token/revokeById/**").permitAll()
.antMatchers("/oauth/token/**").permitAll()
.anyRequest().authenticated()
.and().csrf().disable();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder authenticationManager) throws Exception {
authenticationManager.authenticationProvider(preauthAuthProvider());
authenticationManager.authenticationProvider(dbAuthProvider());
}
#Bean
#Qualifier(value = "authenticationManagerBean")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean(value="preAuthProvider")
public PreAuthenticatedAuthenticationProvider preauthAuthProvider() {
PreAuthenticatedAuthenticationProvider preauthAuthProvider = new PreAuthenticatedAuthenticationProvider();
LOGGER.info("Setting customPreauthenticatedUserDetailsService");
preauthAuthProvider.setPreAuthenticatedUserDetailsService(customPreauthenticatedUserDetailsService);
return preauthAuthProvider;
}
#Bean(value="dbAuthProvider")
public DaoAuthenticationProvider dbAuthProvider() {
DaoAuthenticationProvider dbAuthProvider = new DaoAuthenticationProvider();
dbAuthProvider.setUserDetailsService(userDetailsService);
dbAuthProvider.setPasswordEncoder(passwordEncoder);
return dbAuthProvider;
}
}
On the AuthorizationServerConfig side:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private static final Integer ACCESS_TOKEN_VALIDITY_SECONDS = 300;
private static final Integer REFRESH_TOKEN_VALIDITY_SECONDS = 60 * 60 * 24 * 60;
#Autowired
public AuthorizationServerConfig(AuthenticationManager authenticationManagerBean, PasswordEncoder passwordEncoder, CustomTokenEnhancer customTokenEnhancer, TokenStore tokenStore, JwtAccessTokenConverter accessTokenConverter) {
this.authenticationManagerBean = authenticationManagerBean;
this.passwordEncoder = passwordEncoder;
this.customTokenEnhancer = customTokenEnhancer;
this.tokenStore = tokenStore;
this.accessTokenConverter = accessTokenConverter;
}
private AuthenticationManager authenticationManagerBean;
private PasswordEncoder passwordEncoder;
private CustomTokenEnhancer customTokenEnhancer;
private JwtAccessTokenConverter accessTokenConverter;
private TokenStore tokenStore;
#Override
public void configure(AuthorizationServerSecurityConfigurer authorizationServerSecurityConfigurer) {
authorizationServerSecurityConfigurer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clientDetailsServiceConfigurer) throws Exception {
clientDetailsServiceConfigurer.inMemory().withClient("someclient")
.authorizedGrantTypes("password", "refresh_token")
.scopes("read", "write").accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS)
.refreshTokenValiditySeconds(REFRESH_TOKEN_VALIDITY_SECONDS)
.secret(this.passwordEncoder.encode("somepassword"));
}
#Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(customTokenEnhancer, accessTokenConverter));
endpoints.tokenStore(tokenStore).tokenEnhancer(tokenEnhancerChain)
.authenticationManager(this.authenticationManagerBean);
}
}
Upon start up of the app, the first sign of trouble I see is:
s.c.a.w.c.WebSecurityConfigurerAdapter$3 : No authenticationProviders and no parentAuthenticationManager defined. Returning null.
Oddly, that appears to be a lie, or it's talking about something else, because when I try to login with username and password - it works, and I get both my refresh and access tokens. The debugger shows that the ProviderManager has both my custom authentication provider classes and uses them for authentication.
However, when I try to retrieve a new token with the refresh token, the debugger shows that the app appears to be taking a different ProviderManager path - this ProviderManager only has PreAuthenticatedAuthenticationProvider in its list, and that provider is NOT the one I configured. This PreAuthenticatedAuthenticationProvider is trying to retrieve a UserDetailsService from WebSecurityConfigurerAdapter$UserDetailsServiceDelegator, and the result is an error:
2019-06-08 13:27:24.764 ERROR 8731 --- [nio-8080-exec-3] o.s.s.o.provider.endpoint.TokenEndpoint : Handling error: IllegalStateException, UserDetailsService is required.
So, what configuration step am I missing? Why does the refresh token call go elsewhere? Is it the authenticationManagerBean that I'm passing in the AuthorizationServerConfig?

Authorization Error handling when Authorization service down in Spring boot security doesn't work

In my spring boot web app, I hit a third party service for Authorization and my application is just a content provider. Parent application uses site minder for authentication. My application gets user Id in header and makes call to third party api to set UserDetails with authorities.
My requirement is to handle scenario when third party service for authorization is down. Currently in this case I set UserDetails with no roles and since every endpoint is bound by authorization so I get 403 if third party service for authorization is down.
But I want to display different message if user lacks authorization and if authorization service is down.
If I handle authorization service down by throwing custom exception from UserDetailsServiceImpl -> loadUserByUserName() then RequestHeaderAuthenticationFilter encounters this exception and request gets filtered out. Any idea how to get this done ?
SecurityConfiguration
public class WebSecurityCustomConfig extends WebSecurityConfigAdapter {
private UserDetailsService userDetails;
protected void configure(HttpSecurity http) {
http.csrf().disable().authorizeRequests().antMatchers("/*).permitAll()
.anyRequests()
.hasAnyAuthority("MODULEX","MODULEY");
http.addFilterBefore(requestHeaderAuthFilter(),
BasicAuthenticationFilter.class);
http.exceptionHandling().authenticationEntryPoint(customEntryPoint());
}
protect void configure(AuthenticationManagerBuilder builder) {
PreAuthenticaticatedAuthenticationProvider auth = new
PreAuthenticaticatedAuthenticationProvider ();
auth.setPreAuthenticatedUserDetailsService(new
UserDetailsByNameServiceWrapper<>(userDetails));
}
}
Custom UserDetailsService
public class CustomUserDetailsService implements UserDetailsService {
private final AuthorizationService authService;
#Inject
public CustoUserDetailsService(AuthorizationService authService) {
this.authService = authService;
}
public UserDetails loadUserByUsername(String username) {
return new User(username, "",
authService.getAuthorities(username));
// authService is a third party jar and if their upstream service
//is down , it throws a runtime exception
}
}
If I handle their error as follows then I end up with 403 but I want 503 in case service is down and 403 if user doesnt have right authority for endpoint he is accessing.
current handling auth service exception
public UserDetails loadUserByUsername(String username) {
try{
return new User(username, "",
authService.getAuthorities(username));
}
catch(AuthServiceException e) {
return new User(username, "",
Collections.emptyList());
}
}
Implement AuthenticationEntryPoint and override commence() method as follows.
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
And after that create a method in your WebSecurityCustomConfig class that initialize the RestAuthenticationEntryPoint class as follows.
public AuthenticationEntryPoint authenticationEntryPoint() {
RestAuthenticationEntryPoint ep = new RestAuthenticationEntryPoint();
return ep;
}
Then change your following line to http.exceptionHandling().authenticationEntryPoint(customEntryPoint());
to
http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint());

Implement Spring Security for Rest Api

I use this code for Rest API authentication:
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
Optional<String> basicToken = Optional.ofNullable(request.getHeader(HttpHeaders.AUTHORIZATION))
.filter(v -> v.startsWith("Basic"))
.map(v -> v.split("\\s+")).filter(a -> a.length == 2).map(a -> a[1]);
if (!basicToken.isPresent()) {
return sendAuthError(response);
}
byte[] bytes = Base64Utils.decodeFromString(basicToken.get());
String namePassword = new String(bytes, StandardCharsets.UTF_8);
int i = namePassword.indexOf(':');
if (i < 0) {
return sendAuthError(response);
}
String name = namePassword.substring(0, i);
String password = namePassword.substring(i + 1);
// Optional<String> clientId = authenticationService.authenticate(name, password, request.getRemoteAddr());
Merchants merchant = authenticationService.authenticateMerchant(name, password, request.getRemoteAddr());
if (merchant == null) {
return sendAuthError(response);
}
request.setAttribute(CURRENT_CLIENT_ID_ATTRIBUTE, merchant.getId());
return true;
}
How I can rewrite the code with Spring Security in order to get the same result but for different links to have authentication? For example:
localhost:8080/v1/notification - requests should NOT be authenticated.
localhost:8080/v1/request - requests should be authenticated.
Here you can find a working project https://github.com/angeloimm/springbasicauth
I know in the pom.xml file there are a lot of useless dependencies but I started from an already existing project and I had no time to depure it
Basically you must:
configure spring security
configure spring mvc
implements your own authentication provider according to spring security. Note I used an inMemoryAuthentication. Please modify it according to yuor own wishes
Let me explain the code.
Spring MVC Configuration:
#Configuration
#EnableWebMvc
#ComponentScan(basePackages= {"it.olegna.test.basic"})
public class WebMvcConfig implements WebMvcConfigurer {
#Override
public void configureMessageConverters(final List<HttpMessageConverter<?>> converters) {
converters.add(new MappingJackson2HttpMessageConverter());
}
}
Here we don't do anything else that configuring spring MVC by telling it where to find controllers and so on and to use a single message converter; the MappingJackson2HttpMessageConverter in order to produce JSON responses
Spring Security Configuration:
#Configuration
#EnableWebSecurity
#Import(value= {WebMvcConfig.class})
public class WebSecConfig extends WebSecurityConfigurerAdapter {
#Autowired private RestAuthEntryPoint authenticationEntryPoint;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("test")
.password(passwordEncoder().encode("testpwd"))
.authorities("ROLE_USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/securityNone")
.permitAll()
.anyRequest()
.authenticated()
.and()
.httpBasic()
.authenticationEntryPoint(authenticationEntryPoint);
}
#Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
Here we configure Spring Security in order to use HTTP Basic Authentication for all requests except the ones starting with securityNone. We use a NoOpPasswordEncoder in order to encode the provided password; this PasswrodEncoder does absolutly nothing... it leaves the passwrod as it is.
RestEntryPoint:
#Component
public class RestAuthEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
This entrypoint disables all requests not containg the Authentication header
SimpleDto: a very simple DTO representing the JSON answer form a controller
public class SimpleDto implements Serializable {
private static final long serialVersionUID = 1616554176392794288L;
private String simpleDtoName;
public SimpleDto() {
super();
}
public SimpleDto(String simpleDtoName) {
super();
this.simpleDtoName = simpleDtoName;
}
public String getSimpleDtoName() {
return simpleDtoName;
}
public void setSimpleDtoName(String simpleDtoName) {
this.simpleDtoName = simpleDtoName;
}
}
TestBasicController: a very simple controller
#RestController
#RequestMapping(value= {"/rest"})
public class TestBasicController {
#RequestMapping(value= {"/simple"}, method= {RequestMethod.GET}, produces= {MediaType.APPLICATION_JSON_UTF8_VALUE})
public ResponseEntity<List<SimpleDto>> getSimpleAnswer()
{
List<SimpleDto> payload = new ArrayList<>();
for(int i= 0; i < 5; i++)
{
payload.add(new SimpleDto(UUID.randomUUID().toString()));
}
return ResponseEntity.ok().body(payload);
}
}
So if you try this project by using postman or any other tester you can have 2 scenarios:
authentication required
all ok
Let's suppose you want to invoke the URL http://localhost:8080/test_basic/rest/simple without passing the Authentication header. The HTTP Status code will be 401 Unauthorized
This means that the Authentication Header is required
By adding this header to the request Authorization Basic dGVzdDp0ZXN0cHdk all works pretty good
Note that the String dGVzdDp0ZXN0cHdk is the Base64 encoding of the string username:password; in our case is the Base64 encoding of test:testpwd defined in the inMemoryAuthentication
I hope this is usefull
Angelo
WEB SECURITY USER DATAIL SERVICE
In order to configure Spring security to retrieve user details from DB you must do the following:
create a org.springframework.security.core.userdetails.UserDetailsService implementation like this:
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private BasicService svc;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
BasicUser result = svc.findByUsername(username);
if( result == null )
{
throw new UsernameNotFoundException("No user found with username "+username);
}
return result;
}
}
Inject it to the spring security configuration and use it like this:
public class WebSecConfig extends WebSecurityConfigurerAdapter {
#Autowired private RestAuthEntryPoint authenticationEntryPoint;
#Autowired
UserDetailsService userDetailsService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// auth
// .inMemoryAuthentication()
// .withUser("test")
// .password(passwordEncoder().encode("testpwd"))
// .authorities("ROLE_USER");
auth.userDetailsService(userDetailsService);
auth.authenticationProvider(authenticationProvider());
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/securityNone")
.permitAll()
.anyRequest()
.authenticated()
.and()
.httpBasic()
.authenticationEntryPoint(authenticationEntryPoint);
}
#Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
I pushed the code on the github link I provided. There you can find a full working example based on:
spring 5
spring security 5
hibernate
h2 DB
Feel free to adapt it to your own scenario
You can use a default spring-security configuration described on various websites, like baeldung.com or mkyong.com. The trick in your sample seems to be the call to get the Merchant. Depending on the complexity of the authenticationService and the Merchant object, you can either use the following code, or implement a facade to get similar behaviour.
#Autowired
public void authenticationManager(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(new AuthenticationProvider() {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Merchants merchant = authenticationService.authenticateMerchant(name, password, request.getRemoteAddr());
if(merchant == null) {
throw new AuthenticationException("No Merchant found.");
}
return new UsernamePasswordAuthenticationToken(name, password, merchant.getAuthorities());
}
#Override
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
});
}
Setting the attribute on the request, if necessary could be done by a separate filter which takes the Principal from the SecurityContext and puts it on the request as an attribute.

Custom AuthenticationProvider is not called

I want to have a basic auth-protected REST app. I followed the general instructions from http://www.baeldung.com/spring-security-authentication-provider in order to get the security working.
I ended up creating my implementation of AuthenticationProvider, but it never gets called by Spring. All requests end up with an error:
{"timestamp":1460199213227,"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource","path":"/test"}
without the AuthenticationProvider ever doing anything.
The app is annotation-based and here are the relevant bits:
Security setup
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {
#Autowired
CustomAuthenticationProvider authenticationProvider;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authenticationProvider(authenticationProvider)
.authorizeRequests()
.anyRequest().authenticated().and().httpBasic();
}
}
AuthenticationProvider
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
private UserDAO userDAO;
#Autowired
private Authenticator authenticator;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// This never gets called, I checked with debugger
String username = authentication.getName();
String password = authentication.getCredentials().toString();
User user = userDAO.findByUsername(username);
User authenticatedUser = authenticator.authenticate(user, password);
if (authenticatedUser == null){
throw new RESTAuthenticationException("Auth failed");
}
List<GrantedAuthority> authorityList = new ArrayList<>();
return new UsernamePasswordAuthenticationToken(user, authorityList);
}
#Override
public boolean supports(Class<?> aClass) {
return aClass.equals(UsernamePasswordAuthenticationToken.class);
}
}
Controller
#RestController
public class UserController {
#RequestMapping(value = "/test")
public ResponseEntity test(#AuthenticationPrincipal User user) {
return ResponseEntity.ok().body(user);
}
}
You receive a response with status code 401. This is the "unauthorized" http status code. It is probably caused by a missing/malformed Authorization header in your request.
You are using Http-Basic: it requires the following header in the request :
Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l
where the string QWxhZGRpbjpPcGVuU2VzYW1l is the string <user>:<password> base64 encoded.

Resources