Spring boot 2+ Could not Autowire. There is more than one bean of 'UserDetailsService' - spring-boot

Hello Everyone i'm new in spring security and jwt. I'm implementing Jwt in my spring boot project to secure user login and i'm using spring boot 2.1.5
and i don't know much about new bean restriction in spring boot 2+ .
I need some help .. here i'm trying to #Autowired UserDetailsService and code run fine ..and result is also fine.. but intellij shows error at
#Autowired UserDetailsService jwtUserDetailsService
saying ... Could not autowire. There is more than one bean of UserDetailsService type.
Can anyone explain me what what happens wrong here why i can't autowired and why and what are the Autowired restriction in spring boot 2+ ?
And thanks in advance
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurity extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
private JwtFilter jwtFilter;
#Autowired
private UserDetailsService jwtUserDetailsService; // here i got error only
#Autowired
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests().antMatchers("/api/user/add", "/generate").permitAll().anyRequest().authenticated().and() .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
}
}
my customUserDetailService is
#Service
public class JwtUserDetailService implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (user != null) {
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), new ArrayList<>());
} else {
throw new UsernameNotFoundException("Username does't exists");
}
}
}
My JwtController class which expose restend point to generate jwt token
#CrossOrigin
#RestController
public class JwtController {
#Autowired
private JwtUtils jwtUtils;
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private JwtUserDetailService jwtUserDetailService;
#PostMapping(value = "/generate")
public ResponseEntity<?> generateToken(#RequestBody JwtRequest jwtRequest) throws Exception {
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(jwtRequest.getUsername(),
jwtRequest.getPassword()));
} catch (DisabledException e) {
throw new Exception("USER_DISABLED", e);
} catch (BadCredentialsException e) {
throw new Exception("INVAILD_CREDENTIALS", e);
}
final UserDetails userDetails = jwtUserDetailService.loadUserByUsername(jwtRequest.getUsername());
final String token = jwtUtils.generateToken(userDetails);
return ResponseEntity.ok(new JwtResponse(token));
}
}
My JwtFilter Class
#Component
public class JwtFilter extends OncePerRequestFilter {
#Autowired
private JwtUserDetailService jwtUserDetailService;
#Autowired
private JwtUtils jwtUtils;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String requestTokenHeader = request.getHeader("Authorization");
String username = null;
String jwtToken = null;
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
jwtToken = requestTokenHeader.substring(7);
try {
username = jwtUtils.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
System.out.println("Unable to get JWT Token");
} catch (ExpiredJwtException e) {
System.out.println("JWT Token has expired");
}
} else {
logger.warn("JWT Token does not begin with Bearer String");
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.jwtUserDetailService.loadUserByUsername(username);
if (jwtUtils.validate(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
Other thing as just normal like entity, repository, and some secured restend points

UserDetailsService was provided by spring.
To Autowire you need to configure it with.
#Bean
public UserDetailsService getUserDetails(){
return new JwtUserDetailService(); // Implementation class
}
If you are not interested in Bean Configuration.
you can autowire JwtUserDetailService directly.
#Autowired
private JwtUserDetailService jwtUserDetailsService;

I got the same error in another context. The reason was the Idea donĀ“t know which bean of type 'UserDetailsService' to use.
My solution is through annotation Qualifier:
#Qualifier("beanNameWhichYouWantUse")
#Autowired
private UserDetailsService jwtUserDetailsService;
If use Idea: give mouse point on the error, select from context menu:
"More actions" -> "Add qualifier"
and finally select the bean

You can put this code in application.properties:
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration

Related

How to get JWT authentication to work on spring boot 3

I've been trying to setup spring security on spring boot 3.0.2 to use JWT, but so far, it hasn't worked.
Whenever I start my spring boot API app, none of my endpoints are exposed except /actuator. And I keep getting a default password given to me.
Here's my security config class:
#Configuration
#EnableMethodSecurity
#RequiredArgsConstructor
public class ApiSecurityConfig {
private final AuthenticationTokenFilter authenticationTokenFilter;
private final UserDetailsServiceImpl userDetailsService;
private final CustomAuthenticationEntryPoint entryPoint;
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.exceptionHandling(e -> e.authenticationEntryPoint(entryPoint))
.authorizeHttpRequests(authorizeRequests -> authorizeRequests
.requestMatchers("/merchant/auth/**").permitAll()
.requestMatchers("/swagger-ui.html").permitAll()
.anyRequest().authenticated())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
http.authenticationProvider(authenticationProvider());
http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
EDIT: Here's my authentication token filter:
#Slf4j
#Configuration
public class AuthenticationTokenFilter extends OncePerRequestFilter {
#Autowired
private JwtUtils jwtUtils;
#Autowired
private UserDetailsServiceImpl userDetailsService;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String jwt = "";
String headerAuthorization = request.getHeader("Authorization");
if (StringUtils.hasText(headerAuthorization) && headerAuthorization.startsWith("Bearer ")) {
jwt = headerAuthorization.substring(7, headerAuthorization.length());
}
try {
if (!jwt.isEmpty() && jwtUtils.validateJwt(jwt)) {
String username = jwtUtils.getUsernameFromJwt(jwt);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
} else {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.getWriter().write("{\"status\": \"false\", \"message\": \"Access denied\", \"data\": \"Invalid token\"}");
response.getWriter().flush();
return;
}
} catch (InvalidKeySpecException e) {
log.error("Invalid key spec exception thrown:", e);
} catch (NoSuchAlgorithmException e) {
log.error("No such algorithm exception thrown:", e);
}
}
}
Thanks to to Tangrunze I was able to see that I missed out the filterChain.doFilter(request, response);
But also, I discovered that I had a componentScan annotation in my main class because I was trying to pick up some components in a different maven project.
Apparently, spring security auto configuration kicks in if you scan other base packages before the resident package where your config is in.

Invalid cors request in spring boot

I am running a spring boot application in conjunction with graphql and jwt token. Currently when I am trying to hit one of the endpoints I am getting 'Invalid Cors Request'. Below is the code of config and filter file.Config file:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserService userService;
#Autowired
private JwtFilter jwtFilter;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.userDetailsService(userService);
}
#Bean
public PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
#Bean(name = BeanIds.AUTHENTICATION_MANAGER)
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception{
http.csrf().disable().cors().disable().authorizeRequests().antMatchers("/**", "/graphql", "/graphiql", "/graphql/**", "/graphiql/**")
.permitAll().anyRequest().authenticated()
.and().exceptionHandling().and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
}
}
Filter file:
#Component
#Log4j2
public class JwtFilter extends OncePerRequestFilter {
#Autowired
private JwtUtil jwtUtil;
#Autowired
private UserService userService;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authorizationHeader = request.getHeader("Authorization");
if(authorizationHeader==null)
throw new ServletException();
String token = null;
String userName = null;
if (authorizationHeader.startsWith("Bearer ")) {
token = authorizationHeader.substring(7);
userName = jwtUtil.extractUsername(token);
}
else
throw new ServletException();
if (userName != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userService.loadUserByUsername(userName);
if (jwtUtil.validateToken(token, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
else
throw new ServletException();
}
else
throw new ServletException();
filterChain.doFilter(request, response);
}
}
The rest is a simple graphql project with kickstart implementation of graphql. I only have a couple of queries and I am trying to hit it via Altair extension. Please let me know if any more information is required. Thanks in advance.

Not able to get authentication token on Postman

I am trying to learn Spring security using JWT method. While doing this there is no error in program, but I am not getting token on my Postman client.
Here is my code:
( here I am not dealing with any database, so created fake username and password )
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
if(userName.equals("foo"))
return new User("foo", "foo" , new ArrayList<>());
else
throw new UsernameNotFoundException("User Not Found");
}
}
(Controller code )
public class JwtController {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Autowired
private JwtUtil jwtutil;
#RequestMapping(value = "/token" , method = RequestMethod.POST)
public ResponseEntity<?> generateToken(#RequestBody JwtRequest jwtRequest ) throws Exception
{
System.out.println(jwtRequest);
try
{
this.authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(jwtRequest.getUsername()
, jwtRequest.getPassword()));
}
catch(UsernameNotFoundException e)
{
e.printStackTrace();
throw new Exception("Bad Credentials");
}
UserDetails userDetails = this.customUserDetailsService.loadUserByUsername(jwtRequest.getUsername());
String token = this.jwtutil.generateToken(userDetails);
System.out.println("JWT Token "+ token);
// Now we want to send this token back to client
return ResponseEntity.ok(new JwtResponse(token));
}
}
(Configuration code )
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().cors().disable().authorizeRequests().antMatchers("/token")
.permitAll().anyRequest().authenticated()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception
{
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder()
{
return NoOpPasswordEncoder.getInstance();
}
}
(Jwt Request and response )
public class JwtRequest {
private String username;
private String password;
// getters and setters and constructors
}
public class JwtResponse {
String token;
// getters and setters and constructors
}
Jwtutil class code I have copied from
JwtUtil.java
Postman request
{
"username": "foo",
"password": "foo"
}
Postman response
{
"timestamp": "2021-03-20T05:21:01.251+00:00",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/token"
}
Where am I wrong ? Could anyone help me ?
You have to add #RestController to your JwtController class to make it available :)

Spring Boot Jwt returns access denied

Hey everyone i have problem with jwt with Java.Here is the codes.
Here is returned value from postman
{
"timestamp": "2020-02-29T20:53:35.761+0000",
"status": 403,
"error": "Forbidden",
"message": "Access Denied",
"path": "/login"
}
TokenManager.java
#Service
public class TokenManager {
private static final int expiredAt = 10 * 60 * 60 * 1000;
Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
public String generateToken(String username){
return Jwts.builder().setSubject(username)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expiredAt))
.signWith(key).compact();
}
public boolean tokenValidate(String token){
if(getUserFromToken(token) != null && isExpired(token)) {
return true;
}
return false;
}
public String getUserFromToken(String token){
Claims claims = getClaims(token);
return claims.getSubject();
}
public boolean isExpired(String token){
Claims claims = getClaims(token);
return claims.getExpiration().after(new Date(System.currentTimeMillis()));
}
private Claims getClaims(String token) {
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
}
}
And then JwtTokenFilter.java
#Component
public class JwtTokenFilter extends OncePerRequestFilter {
#Autowired
private TokenManager tokenManager;
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest,
#NotNull HttpServletResponse httpServletResponse,
#NotNull FilterChain filterChain) throws ServletException, IOException {
final String authHeader = httpServletRequest.getHeader("Authorization");
String username = null;
String token = null;
if (authHeader != null && authHeader.contains("Bearer")) {
token = authHeader.substring(7);
try {
username = tokenManager.getUserFromToken(token);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
if (username != null && token != null
&& SecurityContextHolder.getContext().getAuthentication() == null) {
if (tokenManager.tokenValidate(token)) {
UsernamePasswordAuthenticationToken upassToken =
new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>());
upassToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
SecurityContextHolder.getContext().setAuthentication(upassToken);
}
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
And my custom UserDetailService
#Service
public class CustomUserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userRepository.findByUsername(username);
}
}
Here is WebSecurityConfig
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtTokenFilter tokenFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().antMatchers("/signup","/login").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class);
}
#Bean
public AuthenticationManager getAuthenticationManager() throws Exception {
return super.authenticationManagerBean();
}
}
And last one is my controller.I checked the request body and and print the data it just work fine but /login path returns access denied.
#RestController
public class UserController {
#Autowired
private UserService userService;
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private TokenManager tokenManager;
public UserController(UserService userService, AuthenticationManager authenticationManager, TokenManager tokenManager) {
this.userService = userService;
this.authenticationManager = authenticationManager;
this.tokenManager = tokenManager;
}
#RequestMapping(value = "/signup", method = RequestMethod.POST)
public ResponseEntity<User> signup(#RequestBody User user){
return ResponseEntity.ok(userService.save(user));
}
#RequestMapping(value = "/login", method = RequestMethod.POST)
public ResponseEntity<String> login(#Valid #RequestBody AuthRequest authRequest){
try{
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(authRequest.getUsername(),authRequest.getPassword()));
return ResponseEntity.ok(tokenManager.generateToken(authRequest.getUsername()));
}catch (Exception e){
throw e;
}
}
}
When I remove authenticationManager.authenticate method inside login function it returns a valid token.But when I add authenticationManager again it returns access denied.
Actually you did not setup the AuthenticationManager properly.
in your code, you just used the default authentication manager. And it is ok, as there is one default implementation shipped in Spring boot security, which is ProviderManager. what [ProviderManager][1] does is:
Iterates an Authentication request through a list of AuthenticationProviders.
So you need at least one AuthenticationProvider
There are quite some AuthenticationProviders, for example:
AnonymousAuthenticationProvider, NullAuthenticationProvider, DaoAuthenticationProvider, LdapAuthenticationProvider etc
And in your case, you are authenticating against database, so the DaoAuthenticationProvider is the choice.
And Spring security has a very easy way to configure the DaoAuthenticationProvider, and actually, it automatically created one for you when you set userDetailsService to the AuthenticationManagerBuilder to configure your AuthenticationManager, code like this:
#Autowired
private CustomUserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
so all you need to do is add the code snipet above to your WebSecurityConfig
And it is also recommended to use PasswordEncoder instead of storing your password as plain text. A simple way is to use BCryptPasswordEncoder to encode your password before save the user to db...
#Autowired
private CustomUserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}

Spring OAuth2 - Authorization Server - Differentiate users on clients

i'm stuck with my application. It's sound simple: I have two clients registered on my OAuth AuthorizationServer and two users. User alpha can access both apps ("androidapp" and "angularapp"), but user beta only can access to one of these applications (only "angularapp"). How I can differentiate the users and block beta for the "androidapp" app?
This is my AuthServer's code:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter{
#Autowired private DataSource dataSource;
#Autowired private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
;
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("angularapp")
.secret(passwordEncoder.encode("12345"))
.scopes("read", "write")
.authorizedGrantTypes("password", "refresh_token")
.accessTokenValiditySeconds(20000)
.refreshTokenValiditySeconds(20000)
.and()
.withClient("androidapp")
.secret(passwordEncoder.encode("67890"))
.scopes("read", "write")
.authorizedGrantTypes("password", "refresh_token")
.accessTokenValiditySeconds(20000)
.refreshTokenValiditySeconds(20000);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter())
;
}
#Bean
public JwtTokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter jwt = new JwtAccessTokenConverter();
jwt.setSigningKey(JwtConfig.RSA_PRIVATE_KEY);
jwt.setVerifierKey(JwtConfig.RSA_PUBLIC_KEY);
return jwt;
}
}
Thanks in advance for your answers.
My solution here:
When the loadClientByClientId method is executed, the Principal object
stored in SecurityContext doesn't yet exist, but it does when the
loadUserByUsername method is executed with a slight observation: The
Principal object at this point contains the client_id, not the
username, resulting in customizing the UserDetailsService object
instead of ClientsDetailsService. Then, with a relational entity (JPA)
I joined the client_id with the username giving the expected result.
So, the code for UserDetailsService implements is:
#Service
public class UsuarioService implements IUsuarioService, UserDetailsService{
private Logger logger = LoggerFactory.getLogger(UsuarioService.class);
#Autowired
private IUsuarioDao usuarioDao;
#Override
#Transactional(readOnly=true)
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Usuario usuario = usuarioDao.findByUsername(username);
if( usuario == null ) {
logger.error("Login error: Username not found in storage");
throw new UsernameNotFoundException("Login error: Username not found in storage");
}
List<GrantedAuthority> authorities = usuario.getRoles().stream().map( role -> new SimpleGrantedAuthority( role.getNombre() )).collect(Collectors.toList());
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String applicationID = "";
if (principal instanceof UserDetails) {
applicationID = ((UserDetails)principal).getUsername();
} else {
applicationID = principal.toString();
}
logger.info("Application: {} ", applicationID);
if( applicationID == null || applicationID.isEmpty() ) {
logger.error("Application ID can't be empty");
throw new InsufficientAuthenticationException("Application ID can't be empty");
}
OAuthClientDetails app = findApplicationByUsername( usuario.getClientes(), applicationID);
if( app == null ) {
logger.error("Unauthorized user for application {}", applicationID);
throw new UnapprovedClientAuthenticationException("Unauthorized user for application " + applicationID);
}
return new User(username, usuario.getPassword(), usuario.getEnabled(), true, true, true, authorities);
}
private OAuthClientDetails findApplicationByUsername( final List<OAuthClientDetails> list, final String clientID ){
return list.stream().filter( p -> p.getClientId().equals(clientID) ).findAny().orElse(null); } }
And the AuthorizationServer config is:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter{
#Autowired DataSource dataSource;
#Autowired #Qualifier("authenticationManagerBean") private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
;
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter())
;
}
#Bean
public JwtTokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter jwt = new JwtAccessTokenConverter();
jwt.setSigningKey(JwtConfig.RSA_PRIVATE_KEY);
jwt.setVerifierKey(JwtConfig.RSA_PUBLIC_KEY);
return jwt;
}
}
Very grateful for the help and ideas.
The way I have solved in the past was to create a subclass of ResourceOwnerPasswordTokenGranter and override this method:
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
If you copy the original method from the Spring sources, you have at a certain point access to the client_id (client.getClientId()) and the user (userAuth.getPrincipal()).
If the role of the user does not match with the client, I throw an InsufficientAuthenticationException to avoid that the user can log in.
It would be great if Spring Security would have some kind of callback there to avoid having to copy parts of the code to be able to do this. I have opened https://github.com/spring-projects/spring-security-oauth/issues/791 for this.

Resources