Is it neseccary to use AuthenticationManager to load user from database when authorizing in Spring security? - spring-security

I have a controller that authorizes user:
#PostMapping
public ResponseEntity<Token> signInUser(Credentials credentials) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
credentials.getUsername(),
credentials.getPassword())
);
Token token = tokenService.generateToken(authentication);
return ResponseEntity.ok(token);
}
"Credentials" is just a simple data object, generated by swagger:
Credentials
AuthenticationManager bean:
#Bean
public AuthenticationManager authenticationManager(UserDetailsService userDetailsService) {
var authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
return new ProviderManager(authProvider);
}
As I understand, AuthenticationManager only retrieves user from the database and checks if passwords match. And I would like to write all this logic myself. Do I have to write my own implementation of AuthenticationManager or I can just write UserService that will not implement anything?
For example, like these:
UserController:
#PostMapping
public ResponseEntity<Token> signInUser(Credentials credentials) {
Token token = userService.signIn(credentials);
return ResponseEntity.ok(token);
}
UserService:
public Token signIn(Credentials credentials) {
Optional<UserEntity> userEntityOpt =
userEntityRepo.findByUsername(credentials.getUsername());
return tokenService.generateToken(userEntityOpt.get());
}
Validation and stuff omitted

Related

spring security: Generate authentication from jwt

Here my configuration:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration {
#Bean
public JwtDecoder reactiveJwtDecoder() throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec("JAC1O17W1F3QB9E8B4B1MT6QKYOQB36V".getBytes(), mac.getAlgorithm());
return NimbusJwtDecoder.withSecretKey(secretKey)
.macAlgorithm(MacAlgorithm.HS256)
.build();
}
#Bean
public SecurityFilterChain securityFilterChain(
HttpSecurity http
) throws Exception {
Customizer<OAuth2ResourceServerConfigurer<HttpSecurity>> oauth2Customizer = (config) -> config.jwt();
return http
.httpBasic().disable()
.csrf().disable()
.formLogin().disable()
.anonymous().disable()
.logout().disable()
.authorizeHttpRequests((authorize) -> authorize
.antMatchers("/actuator/**").permitAll()
.antMatchers("/gicar/**").permitAll()
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2Customizer)
.build();
}
}
Everything works fine.
I need to allow method execution according to jwt token information.
I know that in order to get that, I need to transform the jwt token to an Authentication object.
From my configuration on, what should I add to it, in order to generate an Authentication object and translate claims to authorities?
What I need to do is something like this:
#RestController
#RequestMapping(value = "/qdcf")
#RequiredArgsConstructor
#Timed
public class QdCFController {
private final UsuariRepository usuariRepository;
#GetMapping("/user")
#PreAuthorize("hasRole(ADMIN)")
public Optional<Usuari> user() {
return this.usuariRepository.findOne(UsuariSpecs.hasCodi("11111111A"));
}
}
Your configuration is already enough for Spring to create an Authentication object, the question is how it's going to get roles from your JWT.
To "guide" Spring and your JwtDecoder you should create and configure a bean of JwtAuthenticationConverter type, e.g. like this:
#Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
final JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
// choose a JWT claim name where authorities are stored on login
// authorities claim name defaults to "scope" and "scp" if this method is not used
grantedAuthoritiesConverter.setAuthoritiesClaimName("roles");
// here choose a scope prefix that was used
// prefix defaults to "SCOPE_" if this method is not used
grantedAuthoritiesConverter.setAuthorityPrefix("");
final JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
return jwtAuthenticationConverter;
}
This configuration will help JwtDecoder to translate any authority in the "roles" claim of JWT to GrantedAuthority with no prefix.

How can a jwt protected resource server call userinfo?

The documentation at spring security is missing important detail. Our idp does not provide an introspection link, and our resource server is not a client in its own right. It receives JWT access tokens from the actual client, and "needs to know" details about the user associated with the access token.
In our case standard jwt processing gives us a useful start, but we need to fill out the authentication with the claims from userinfo.
How do we 1. get a baseline valid oauth2 authentication, 2. fill it out with the results of the userinfo call.
public class UserInfoOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
private final OpaqueTokenIntrospector delegate =
new NimbusOpaqueTokenIntrospector("https://idp.example.org/introspect", "client", "secret");
private final WebClient rest = WebClient.create();
#Override
public OAuth2AuthenticatedPrincipal introspect(String token) {
OAuth2AuthenticatedPrincipal authorized = this.delegate.introspect(token);
return makeUserInfoRequest(authorized);
}
}
Current implementation using a converter:
#Configuration
public class JWTSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired JwtConverterWithUserInfo jwtConverter;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf().disable()
.authorizeRequests(authz -> authz
.antMatchers("/api/**").authenticated()
.anyRequest().permitAll())
.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtConverter);
}
}
#Configuration
public class WebClientConfig {
/**
* Provides a Web-Client Bean containing the bearer token of the authenticated user.
*/
#Bean
WebClient webClient(){
HttpClient httpClient = HttpClient.create()
.responseTimeout(Duration.ofSeconds(5))
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000);
return WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.filter(new ServletBearerExchangeFilterFunction())
.build();
}
}
#Component
#Log4j2
public class JwtConverterWithUserInfo implements Converter<Jwt, AbstractAuthenticationToken> {
#Autowired WebClient webClient;
#Value("${userinfo-endpoint}")
String userinfoEndpoint;
#SuppressWarnings("unchecked")
#Override
public AbstractAuthenticationToken convert(Jwt jwt) {
String token = jwt.getTokenValue();
log.debug("Calling userinfo endpoint for token: {}", token);
String identityType = jwt.getClaimAsString("identity_type");
Map<String,Object> userInfo = new HashMap<>();
if ("user".equals(identityType)) {
// invoke the userinfo endpoint
userInfo =
webClient.get()
.uri(userinfoEndpoint)
.headers(h -> h.setBearerAuth(token))
.retrieve()
.onStatus(s -> s.value() >= HttpStatus.SC_BAD_REQUEST, response -> response.bodyToMono(String.class).flatMap(body -> {
return Mono.error(new HttpException(String.format("%s, %s", response.statusCode(), body)));
}))
.bodyToMono(Map.class)
.block();
log.debug("User info Map is: {}",userInfo);
// construct an Authentication including the userinfo
OidcIdToken oidcIdToken = new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaims());
OidcUserInfo oidcUserInfo = new OidcUserInfo(userInfo);
List<OidcUserAuthority> authorities = new ArrayList<>();
if (oidcIdToken.hasClaim("scope")) {
String scope = String.format("SCOPE_%s", oidcIdToken.getClaimAsString("scope"));
authorities.add(new OidcUserAuthority(scope, oidcIdToken, oidcUserInfo));
}
OidcUser oidcUser = new DefaultOidcUser(authorities, oidcIdToken, oidcUserInfo, IdTokenClaimNames.SUB);
//TODO replace this OAuth2 Client authentication with a more appropriate Resource Server equivalent
return new OAuth2AuthenticationTokenWithCredentials(oidcUser, authorities, oidcUser.getName());
} else {
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
if (jwt.hasClaim("scope")) {
authorities.add(new SimpleGrantedAuthority(String.format("SCOPE_%s", jwt.getClaimAsString("scope"))));
}
return new JwtAuthenticationToken(jwt, authorities);
}
}
}
public class OAuth2AuthenticationTokenWithCredentials extends OAuth2AuthenticationToken {
public OAuth2AuthenticationTokenWithCredentials(OAuth2User principal,
Collection<? extends GrantedAuthority> authorities,
String authorizedClientRegistrationId) {
super(principal, authorities, authorizedClientRegistrationId);
}
#Override
public Object getCredentials() {
return ((OidcUser) this.getPrincipal()).getIdToken();
}
}
Instead of a custom OpaqueTokenIntrospector, try a custom JwtAuthenticationConverter:
#Component
public class UserInfoJwtAuthenticationConverter implements Converter<Jwt, BearerTokenAuthentication> {
private final ClientRegistrationRepository clients;
private final JwtGrantedAuthoritiesConverter authoritiesConverter =
new JwtGrantedAuthoritiesConverter();
#Override
public BearerTokenAuthentication convert(Jwt jwt) {
// Spring Security has already verified the JWT at this point
OAuth2AuthenticatedPrincipal principal = invokeUserInfo(jwt);
Instant issuedAt = jwt.getIssuedAt();
Instant expiresAt = jwt.getExpiresAt();
OAuth2AccessToken token = new OAuth2AccessToken(
BEARER, jwt.getTokenValue(), issuedAt, expiresAt);
Collection<GrantedAuthority> authorities = this.authoritiesConverter.convert(jwt);
return new BearerTokenAuthentication(principal, token, authorities);
}
private OAuth2AuthenticatedPrincipal invokeUserInfo(Jwt jwt) {
ClientRegistration registration =
this.clients.findByRegistrationId("registration-id");
OAuth2UserRequest oauth2UserRequest = new OAuth2UserRequest(
registration, jwt.getTokenValue());
return this.oauth2UserService.loadUser(oauth2UserRequest);
}
}
And then wire into the DSL like so:
#Bean
SecurityFilterChain web(
HttpSecurity http, UserInfoJwtAuthenticationConverter authenticationConverter) {
http
.oauth2ResourceServer((oauth2) -> oauth2
.jwt((jwt) -> jwt.jwtAuthenticationConverter())
);
return http.build();
}
our resource server is not a client in its own right
oauth2-client is where Spring Security's support for invoking /userinfo lives and ClientRegistration is where the application's credentials are stored for addressing /userinfo. If you don't have those, then you are on your own to invoke the /userinfo endpoint yourself. Nimbus provides good support, or you may be able to simply use RestTemplate.

Success Handler for Multiple Spring Security SAML RelyingPartyRegistrations

I am using Spring Security 5.5.3 to hit two separate SAML Identity Providers (IDP) (similar to, say, Google and Facebook, but not OAuth).
So, in my Security configuration, I define the two RelyingPartyRegistration beans:
public RelyingPartyRegistration site1RelyingPartyRegistration() {
RelyingPartyRegistration registration = RelyingPartyRegistration.withRegistrationId("site1")
// rest of configuration
.build();
return registration;
}
public RelyingPartyRegistration site2RelyingPartyRegistration() {
RelyingPartyRegistration registration = RelyingPartyRegistration.withRegistrationId("site2")
// rest of configuration
.build();
return registration;
}
#Bean
public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() throws Exception {
Collection<RelyingPartyRegistration> registrations = Collections.unmodifiableList(
Arrays.asList(
site1RelyingPartyRegistration(),
site2elyingPartyRegistration())
);
InMemoryRelyingPartyRegistrationRepository repository = new InMemoryRelyingPartyRegistrationRepository(registrations);
return repository;
}
Then I define my SuccessHandler:
public class MySuccessHandler implements AuthenticationSuccessHandler {
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException{
// need to determine whether the success came from site1 or site2
}
}
My question is, since I can authenticate from more than one Identity Provider, how can I find out which site it was? The only idea I have is to pull the entire <samlp:Response> from the Saml2AuthenticationToken and parse it to an XML document to get a single String attribute.
Is there an easier way?
In Spring Security 5.6, it's included in the Authentication:
Saml2AuthenticatedPrincipal principal =
(Saml2AuthenticatedPrincipal) authentication.getPrincipal();
String registrationId = principal.getRelyingPartyRegistrationId();
Before Spring Security 5.6, you can customize the Saml2AuthenticatedPrincipal in the authentication provider:
OpenSaml4AuthenticationProvider provider =
new OpenSaml4AuthenticationProvider();
provider.setResponseAuthenticationConverter((params) -> {
Response response = params.getResponse();
Saml2AuthenticationToken token = params.getToken();
RelyingPartyRegistration registration = token.getRelyingPartyRegistration();
String registrationId = registration.getRegistrationId();
// ... create custom authentication that contains the registration id
});

Spring OAuth with JWT custom UserDetails - Set Principal inside JwtAccessTokenConverter

Some additional info is sent from OAuth Authorization Server that is needed inside a custom UserDetails class on Resource Server, and preferably inside SpringSecurity Principal.
Current approach is setting a username as Principal and adding additional info as an additional details of Authentication object like this.
public class CustomAccessTokenConverter extends JwtAccessTokenConverter{
#Override
public OAuth2Authentication extractAuthentication(Map<String, ?> claims) {
OAuth2Authentication authentication = super.extractAuthentication(claims);
CustomUserDetails userDetails = new CustomUserDetails ();
userDetails.setUserId(((Integer)claims.get("id")).longValue());
userDetails.setName((String) claims.get("name"));
userDetails.setLastName((String) claims.get("lastName"));
authentication.setDetails(userDetails);
return authentication;
}
}
The good thing about this approach is we can access custom UserDetails from anywhere inside the app. The bad thing is that Pricipal object is stuck on being only users username, and we need a lot more code to access custom UserDetails.
// preferable way
(UserAuthDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
// current solution
(UserAuthDetails) ((OAuth2AuthenticationDetails) SecurityContextHolder.getContext().getAuthentication().getDetails()).getDecodedDetails();
Is there a cleaner solution to use JwtAccessTokenConverter but still be able to set Principal as custom UserDetails instead of setting it to (useless) username and sending additional info as details of Authentication object?
I can not say if this is the preferred solution, but after trying to solve the same thing myself, I ended up extending the DefaultUserAuthenticationConverter.
So you can do something like this
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
DefaultAccessTokenConverter defaultConverter = new DefaultAccessTokenConverter();
defaultConverter.setUserTokenConverter(new CustomUserAuthenticationConverter());
JwtAccessTokenConverter jwtConverter = new JwtAccessTokenConverter();
converter.setAccessTokenConverter(defaultConverter);
return converter;
}
Then the DefaultUserAuthenticationConverter is not very extendable since most methods and properties are private. But here is an example
public class CustomUserAuthenticationConverter extends DefaultUserAuthenticationConverter {
private static final String CUST_PROP = "custProp";
#Override
public Authentication extractAuthentication(Map<String, ?> map) {
if (map.containsKey(USERNAME) && map.containsKey(CUST_PROP)) {
String username = (String) map.get(USERNAME);
String custProp = (String) map.get(CUST_PROP);
CustomPrincipal principal = new CustomPrincipal();
pricipal.setUsername(username);
pricipal.setCustomProp(custProp);
Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
return new UsernamePasswordAuthenticationToken(user, "N/A", authorities);
}
return null;
}
private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map) {
//Copy this method from DefaultUserAuthenticationConverter or create your own.
}
}

Is it possible to load granted authorities and other user information AFTER authentication succeeds when using a UserDetailsService?

I have been able to get a Spring Security based application up and running, and it has been satisfying all my requirements until now.
I do have 1 doubt regarding how UserDetailsService is used in Spring Security. I have a custom 'UserDetailsService' implementation, which goes like this -
public class CustomUserDetailsService implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<UserDetailsDto> userDetailsByEmail = // Load userDetailsDto from database
if (!userDetailsByEmail.isPresent()) {
throw new UsernameNotFoundException("Username does not exists");
}
UserDetailsDto userDetailsDto = userDetailsByEmail.get();
List<Role> roles = roleService.listByEmail(username);
List<ModulePermission> modulePermissions = modulePermissionService.listByUserId(userDetailsDto.getId());
UserType userType = userTypeService.getByUserId(userDetailsDto.getId());
return new LoggedInUser(userDetailsDto, roles, modulePermissions, userType);
}
}
The class LoggedInUser is an extension of Spring Security's org.springframework.security.core.userdetails.User class, which goes like this -
public class LoggedInUser extends User {
private static final long serialVersionUID = -1L;
private Long userId;
private boolean firstLogin;
private UserType userType;
private List<ModulePermission> modulePermissions;
private String firstName;
private String lastName;
private String contactNo;
public LoggedInUser(UserDetailsDto userDetailsDto, List<Role> roles, List<ModulePermission> modulePermissions,
UserType userType) {
super(userDetailsDto.getEmail(), userDetailsDto.getPassword(), userDetailsDto.getEnabledStatus().getValue(),
userDetailsDto.getAccountNonExpiredStatus().getValue(), true,
userDetailsDto.getAccountNonLockedStatus().getValue(),
roles.stream().map(role -> new SimpleGrantedAuthority(role.getId())).collect(Collectors.toList()));
this.modulePermissions = modulePermissions;
this.userType = userType;
this.userId = userDetailsDto.getId();
this.firstLogin = userDetailsDto.getIsFirstLoginStatus().getValue();
this.firstName = userDetailsDto.getFirstName();
this.lastName = userDetailsDto.getLastName();
this.contactNo = userDetailsDto.getContactNo();
}
public List<ModulePermission> getModulePermissions() {
return Collections.unmodifiableList(modulePermissions);
}
public UserType getUserType() {
return userType;
}
public Long getUserId() {
return userId;
}
public boolean isFirstLogin() {
return firstLogin;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public String getContactNo() {
return contactNo;
}
public void setFirstLogin(boolean firstLogin) {
this.firstLogin = firstLogin;
}
}
Now, to configure Spring Security to use my CustomUserDetailsService, I do the following in security configuration -
#Bean
public UserDetailsService customUserDetailsService() {
return new CustomUserDetailsService();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher(SuperAdminConstant.UrlConstant.ANT_MATCHER_PATH)
.userDetailsService(customUserDetailsService())
.formLogin(// further configuration)
}
And this works without any problems.
But notice that in CustomUserDetailsService, several database queries are executed even before the user has been authenticated successfully (This is because Spring Security has created a DaoAuthenticationProvider, which loads a UserDetails implementation (in my case, LoggedInUser), and perform various checks on that object AFTER it has been retrieved from a UserDetailsService (in my case , CustomUserDetailsService)).
Consider that a user has entered the correct username, but a wrong password. In that case, the high-level authentication flow would be -
CustomUserDetailsService would be called
First query is executed to verify username and load user details (UsernameNotFoundException is not thrown as username is correct)
Second query is executed to retrieve the roles
Third query is executed to retrieve module permissions
Fourth query is executed to retrieve user types
DaoAuthenticationProvider checks the password, finds it to be incorrect, and throws a BadCredentialsException.
So as can be seen, total 4 queries are executed EVEN BEFORE authentication process has completed, out of which only 1 is essential at this stage (the first query to verify username).
One solution to this problem can be to eliminate the use of UserDetailsService altogeather, and use a custom AuthenticationProvider instead.
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Override
Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// Customize the authentication logic here, and retrieve
// user information only if everything is correct.
}
}
But going by this approach also means that I have to duplicate code and functionality provided by DaoAuthenticationProvider and AbstractUserDetailsAuthenticationProvider, which includes checking user account status flags manually (accountNonExpired, accountNonLocked etc.), and throwing exceptions.
So I was wondering weather it is possible to perform authentication logic in such a way that user information is retrieved only AFTER authentication succeeds, and most of the authentication logic provided by Spring Security can be resused.
Any ideas will be deeply appreciated.
You can write implementation of AuthenticationSuccessHandler:
#Component
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
LoggedInUser loggedInUser = (LoggedInUser)authentication.getPrincipal();
List<Role> roles = roleService.listByEmail(username);
List<ModulePermission> modulePermissions = modulePermissionService.listByUserId(userDetailsDto.getId());
UserType userType = userTypeService.getByUserId(userDetailsDto.getId());
// Set roles after authentication succeeds
loggedInUser.setRoles(roles);
loggedInUser.setModulePermissions(modulePermissions);
loggedInUser.setUserType(userType);
}
}
After the authentication succeeds, you can obtain logged in user from security context and set additional properties.

Resources