for my REST API I am using JWT's for OAuth2 authorization. Currently I am extending JwtTokenStore to store the refresh tokens in memory so that I am able to revoke them.
// TODO: This is a temporary in memory solution that needs to be replaced with a concrete persistent implementation.
public class MyJwtTokenStore extends JwtTokenStore {
private List<OAuth2RefreshToken> refreshTokens;
public MyJwtTokenStore(JwtAccessTokenConverter jwtTokenEnhancer) {
super(jwtTokenEnhancer);
refreshTokens = new ArrayList<>();
}
#Override
public OAuth2RefreshToken readRefreshToken(String tokenValue) {
OAuth2RefreshToken refreshToken = super.readRefreshToken(tokenValue);
if (!refreshTokens.contains(refreshToken)) {
throw new InvalidGrantException("Invalid refresh token: " + tokenValue);
}
return refreshToken;
}
#Override
public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) {
refreshTokens.add(refreshToken);
}
#Override
public void removeRefreshToken(OAuth2RefreshToken token) {
refreshTokens.remove(token);
}
}
I would like start storing these refresh tokens in a database rather than in memory. Spring provides us with JdbcTokenStore, but if I extend that class then I am unable to set a JwtAccessTokenConverter in the constructor. I know that I could just implement my own method of saving/retrieving the JWTs but I would like to take advantage of the out of the box support for the schema at https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql which the JdbcTokenStore provides.
create table oauth_refresh_token (
token_id VARCHAR(256),
token LONGVARBINARY,
authentication LONGVARBINARY
);
Does Spring support storing JWT in a data source? I need something like a "JwtJdbcTokenStore". What is a good way to go about doing this but still use the predefined queries and operations from JdbcTokenStore?
No, Spring doesn't supports this. Refer to this thread
https://github.com/spring-projects/spring-security-oauth/issues/687
Persisting JWT tokens is irrelevant since JWT tokens are self contained, everything you need to know is already available in that token.
Having said that, if you have requirement for persisting them, then you will have to write custom logic for the same.
Related
Please bare with me as I am learning Spring Data REST as I go. Definitely feel free to suggest a safer approach if what I am proposing here is not the safest approach or even possible.
Problem
A user logs into my Spring Data REST API via Google OAuth2. After logging in, I need to get the user's ID, which is just the value of their primary key, from the User table. The reason I need the ID is to restrict access to endpoints such as /users/{id}. If a user's ID is 1, then he is only allowed to view /users/1, unless he is an Administrator.
Current Login Architecture
This portion of the application works as expected:
OAuth2AuthenticationSuccessHandler.java:
#Component("oauth2authSuccessHandler")
public class OAuth2AuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Autowired
private UserDataRestRepository userRepository;
#Autowired
private RandomStringGenerator randomStringGenerator;
#Autowired
private PasswordEncoder passwordEncoder;
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
OAuth2AuthenticationToken authenticationToken = (OAuth2AuthenticationToken) authentication;
String email = authenticationToken.getPrincipal().getAttributes().get("email").toString();
if (!userRepository.existsByEmail(email)) {
// If email not found, create local user account.
String firstName = CaseUtils.toCamelCase(
authenticationToken.getPrincipal().getAttributes().get("given_name").toString().toLowerCase(),
true);
String lastName = CaseUtils.toCamelCase(
authenticationToken.getPrincipal().getAttributes().get("family_name").toString().toLowerCase(),
true);
// Generate temporary username.
BigInteger usernameSuffix = userRepository.getNextValSeqUserUsername();
String username = "User" + usernameSuffix;
// Generate temporary password.
String password = randomStringGenerator.generate(20).toUpperCase();
// Encode Password.
String encodedPassword = passwordEncoder.encode(password);
User newUser = new User();
newUser.setFirstName(firstName);
newUser.setLastName(lastName);
newUser.setUsername(username);
newUser.setUserSetUsername(false);
newUser.setEmail(email);
newUser.setPassword(encodedPassword);
newUser.setUserSetPassword(false);
newUser.setCreated(new Timestamp(System.currentTimeMillis()));
newUser.setDisabled(false);
newUser.setLocked(false);
userRepository.save(newUser);
}
// Redirect to root page.
redirectStrategy.sendRedirect(request, response, "/");
}
}
UserDataRestRepository.java:
#RepositoryRestResource(collectionResourceRel = "users", path = "users")
public interface UserDataRestRepository extends PagingAndSortingRepository<User, Integer>, CrudRepository<User, Integer> {
public boolean existsByEmail(String email);
#Query(value="SELECT NEXT VALUE FOR Seq_User_Username", nativeQuery=true)
public BigInteger getNextValSeqUserUsername();
}
OAuth2LoginSecurityConfig.java:
#Configuration
#EnableWebSecurity
public class OAuth2LoginSecurityConfig {
#Autowired
private AuthenticationSuccessHandler oauth2authSuccessHandler;
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authorize -> authorize
.anyRequest()
.authenticated()
)
.oauth2Login()
.successHandler(oauth2authSuccessHandler);
return http.build();
}
}
Database Tables
User:
Role:
UserRole:
Current Post-authentication Problems
Currently, after a user logs in via OAuth2, we do not have access to the user's ID (which is the value of their primary key in the User table) unless we access the Authentication object, get the email address, then get the user's ID from the User table based on the email address. Basically: Select Id from User where Email = user#example.com
Proposed Login Architecture
I am wondering if the UserDetailsService can solve the problem, illustrated in green:
My train of thought is: by getting the user's info from the User table, then loading it into the UserDetailsService, the entire application now has access to the user's ID (primary key) and all of the other info in that row via the UserDetailsService.
Thanks for your help.
I would not include a round-trip to the DB for each incoming request on resource-server(s), this is a waste of resources.
Have you considered using an authorization-server in front of Google and either directly bound to your user database, or capable of calling a web-service to fetch additional data? Many propose it, along with "login with Google".
You would have complete control of access and ID tokens claims: you can add about anything as private claim, but the request to the DB (or Web-Service) happens only once per token issuance (and not once per request to a resource-server). And if you define an Authentication of your own (easy, just extend AbstractAuthenticationToken), you can put accessors to cast those private claims from Object to something more relevant to your security / domain model.
I demo something similar (add a private claim to access-token with a value returned by a web-service and then use it for access-control with custom Authentication and security DSL) in this project.
as the title suggests, I have configured security in my Spring WebFlux application by using #EnableWebFluxSecurity and #EnableReactiveMethodSecurity.
I am using RouterFunction to handle the request routing. The following code is for the router:
#Component
public class UserServiceRequestRouter {
#Autowired
private UserServiceRequestHandler requestHandler;
#Bean
public RouterFunction<ServerResponse> route() {
//#formatter:off
return RouterFunctions
.route(GET("/user/{userId}"), requestHandler::getUserDetails);
//#formatter:on
}
}
And the request handler is:
#Component
public class UserServiceRequestHandler {
#Autowired
private UserService userService;
#PreAuthorize("#userServiceRequestAuthorizer.authorizeGetUserDetails(authentication, #request)")
public Mono<ServerResponse> getUserDetails(ServerRequest request) {
//#formatter:off
return userService.getUserDetails(request.pathVariable("userId"))
.convert()
.with(toMono())
.flatMap(
(UserDetails userDetails) -> ServerResponse.ok()
.contentType(APPLICATION_NDJSON)
.body(Mono.just(userDetails), UserDetails.class)
);
//#formatter:on
}
}
Note: The #Autowired UserService is to fetch data from the database in a reactive way.
Next, I have defined a #Component as:
#Component
#SuppressWarnings("unused")
#Qualifier("userServiceRequestAuthorizer")
public class UserServiceRequestAuthorizer {
public boolean authorizeGetUserDetails(JwtAuthenticationToken authentication, ServerRequest request) {
// #formatter:off
if (authentication == null) {
return false;
}
Collection<String> roles = authentication.getAuthorities()
.stream()
.map(Objects::toString)
.collect(Collectors.toSet());
if (roles.contains("Admin")) {
return true;
}
Jwt principal = (Jwt) authentication.getPrincipal();
String subject = principal.getSubject();
String userId = request.pathVariable("userId");
return Objects.equals(subject, userId);
// #formatter:on
}
}
It is notable here that I am using Spring OAuth2 Authorization Server, which is why the parameter authentication is of type JwtAuthenticationToken.
The application is working as per the expectation. But I am wondering if I am doing it the right way, meaning is this the best practice of doing method level Authorization in a reactive way?
The followings are my stack:
JDK 17
org.springframework.boot:3.0.0-M4
org.springframework.security:6.0.0-M6
Any advice you could give would be much appreciated.
Update
As mentioned by M. Deinum in the comment why shouldn't I use hasAuthority("Admin") or principal.subject == #userId, the reason is that the authorization code I provided is merely for demonstration purposes. It can get complicated and even if that complicacy might be managed by SpEL, I would rather not for the sake of simplicity.
Also the question is not about using inline SpEL, it's more about its reactiveness. I don't know if the SpEL mentioned in the #PreAuthorize is reactive! If it is reactive by nature then I can assume any expression mentioned in the #PreAuthorize would be evaluated reactively.
As far as I know, SpEL expressions evaluation is synchronous.
Unless your UserServiceRequestAuthorizer does more than checking access-token claims against static strings or request params and payload, I don't know why this would be an issue: it should be very, very fast.
Of course, if you want to check it against data from DB or a web-service this would be an other story, but I'd say that your design is broken and that this data access should be made once when issuing access-token (and set private claims) rather than once per security evaluation (which can happen several times in a single request).
Side notes
It is notable here that I am using Spring OAuth2 Authorization Server, which is why the parameter authentication is of type JwtAuthenticationToken.
I do not agree with that. It would be the same with any authorization-server (Keycloak, Auth0, Microsoft IdentityServer, ...). You have a JwtAuthenticationToken because you configured a resource-server with a JWT decoder and kept the default JwtAuthenticationConverter. You could configure any AbstractAuthenticationToken instead, as I do in this tutorial.
It can get complicated and even if that complicacy might be managed by SpEL, I would rather not for the sake of simplicity.
I join #M.Deinum point of view, writing your security rules in a service, like you do, makes it far less readable than inlining expressions: hard to guess what is checked while reading the expression => one has to quit current source file, open security service one and read the code.
If you refer to the tutorial already linked above, it is possible to enhance security DSL and write stuff like: #PreAuthorize("is(#username) or isNice() or onBehalfOf(#username).can('greet')") to stick to your sample, this would give #PreAuthorize("is(#userId) or isAdmin()).
Spring Authorization Server OAuth 2.1.
How can i programatically simulate the authorization_code grant?
Since all grants except for authorization_code and client_credentials have been dropped this has become quite a headache.
The scenario calls for a #Scheduled job to login as a specific user where the client credentials are encoded properties within the server performing the login.
The user roles are important when executing downstream resources and is considered a regular user of the registered Client.
Using the Password grant was perfect for this scenario in OAuth 2.0.
Before i start hacking our Spring Auth server and implement a Password grant for registered resources or maybe overloading the client_credentials for user_credentialed payloads.
Quite a pain if you ask me, so please enlighten me? Are there any patterns for implementing this that i have not yet discovered?
While I'm curious what specific use case you have that needs to perform tasks as a particular user (as opposed to a single confidential client), it should still be possible with customization.
maybe overloading the client_credentials for user_credentialed payloads
This approach makes the most sense to me as a way to adapt supported flows in OAuth 2.1 to emulate a deprecated flow like the resource owner password grant. You can use a variation of this github gist, extending it with your user's authorities if needed. One possible solution might look like the following:
#Component
public final class DaoRegisteredClientRepository implements RegisteredClientRepository {
private final RegisteredClient registeredClient;
private final UserDetailsService userDetailsService;
public DaoRegisteredClientRepository(RegisteredClient registeredClient, UserDetailsService userDetailsService) {
this.registeredClient = registeredClient;
this.userDetailsService = userDetailsService;
}
#Override
public void save(RegisteredClient registeredClient) {
throw new UnsupportedOperationException();
}
#Override
public RegisteredClient findById(String id) {
return this.registeredClient.getId().equals(id) ? this.registeredClient : null;
}
#Override
public RegisteredClient findByClientId(String clientId) {
UserDetails userDetails;
try {
userDetails = this.userDetailsService.loadUserByUsername(clientId);
} catch (UsernameNotFoundException ignored) {
return null;
}
return RegisteredClient.from(this.registeredClient)
.clientId(userDetails.getUsername())
.clientSecret(userDetails.getPassword())
.clientSettings(ClientSettings.builder().setting("user.authorities", userDetails.getAuthorities()).build())
.build();
}
}
This uses a single client registration, but makes use of a UserDetailsService to resolve a subject representing your user's username and a secret which is actually the user's password. You would then need to provide an #Bean of type OAuth2TokenCustomizer<JwtEncodingContext> to access the user.authorities setting and add those authorities to the resulting access token (JWT) using whatever claim your resource server expects them in.
Alternatively, you could just override the scopes parameter of the returned RegisteredClient if desired.
I had the similar problem and ended up creating a password grant emulation servlet filter. Please refer to my example:
https://github.com/andreygrigoriev/spring_authorization_server_password_grant
I've used Spring Security multiple times on several projects, including 3 legged OAuth2 authentication on Zuul API Gateway, etc. All works brilliant and official documentation is very neat and simple.
But there is one point that I still don't get from docs. Imagine you have a spring based Resource Server with several ID Providers, and also you have your own user database and form login.
Thus, users can be authenticated either via form login or via one of IDPs (let's say Google or Facebook).
The question is: how to match Authentication from any of your IDPs to Authentication object that is enhanced by/mapped to your local user?
I.e.: Alice has an account in your system (in your database). She goes into her "profile" and declares that she also has a Google or Facebook account. OK, done, you save this info somewhere in your system.
Now, when Alice login into your system via social network, what spring API do you use to understand that Alice entered via Google is exact same Alice that is already registered in your DB? In what API do you enhance her Authentication with authorities based on your DB?
Thanks in advance, guys
The way this is typically done is by creating a composite that contains both the OidcUser object and your domain object. For example:
#Component
public class MyOAuth2UserService implements OAuth2UserService<OidcUserRequest, OidcUser> {
private final OidcUserService delegate = new OidcUserService();
#Override
public OidcUser loadUser(OidcUserRequest oidcUserRequest) {
// the information that comes back from google, et al
OidcUser oidcUser = this.delegate.loadUser(oidcUserRequest);
// the information from your DB
MyUser user = this.myRepository.findUserByXYZ(oidcUser.getXYZ());
return new MyOidcUser(user, oidcUser);
}
private static class MyOidcUser extends MyUser implements OidcUser {
private final OidcUser delegate;
public MyOidcUser(MyUser user, OidcUser oidcUser) {
super(user);
this.delegate = oidcUser;
}
// ... implement delegate methods
}
}
Note that XYZ is some attribute that allows you to know that the user from Google is the user from your system. Maybe that's the email address, for example.
The benefit to this extra bit of work is that Spring Security will place this MyOidcUser object into Authentcation#getPrincipal. So now, if you need to get your domain bits, you do (MyUser) authentication.getPrincipal(), but if you need the OIDC bits, you do (OidcUser) authentication.getPrincipal(). Depending on your use cases, you may be able to do something as simple as:
#GetMapping("/endpoint1")
public String endpoint1(#AuthenticationPrincipal MyUser myUser) {
// ...
}
#GetMapping("/endpoint2")
public String endpoint2(#AuthenticationPrincipal OidcUser oidcUser) {
URL issuer = oidcUser.getIdToken().getIssuer();
// ...
}
In my Spring Social app, I'm trying to integrate certain Social Login functionalities. After being redirected from, for example Twitter, Spring calls the following to look up the user.
public class SimpleSocialUserDetailsService implements SocialUserDetailsService {
#Override
public SocialUserDetails loadUserByUserId(String userId) throws UsernameNotFoundException, DataAccessException {
/*
Commented
*/
}
However, since I will have multiple social login providers, the userId alone is not enough for me to look up the user in my database. I need at least the sign in provider or access token.
Is there anyway to get the sign in provider, or more information, in SocialUserDetailsService? Any other way to solve my problem would be great!
Spring Social is rather Agnostic to the Sign in Providers when properly implemented. I believe you are confused on the flow of Spring Social. At the point you describe spring social has already looked up the connections table and presumably found a record, so it looks up your user table for the user matching with userId (as referenced in the connections table) This is usually associated with the username.
This connection <-> User matching is done in the SocialAuthenticationProvider before calling the SocialUserDetails loadUserByUserId method.
So the SocialAuthenticationProvider already does what you ask for by querying the usersConnectionRepository and comparing the provider connection to find the appropriate user.
Now for your case you would can go ahead and override the user service that you have setup. As long as the userId used on the doPostSignUp call matches the one you look up in the loadUserByUserId, the proper user will be retrieved.
This is a sample:
Wherever your signup logic is executed, you call the doPostSignup and pass the desired user id (Username or another uniquely identifiable String)
ProviderSignInUtils.doPostSignUp(variableForNewUserObject.getId().toString(), request);
Now you Override the loadUserByUserId in SimpleSocialUserDetailsService
#Autowired
private UserDetailsService userDetailsService;
#Override
public SocialUserDetails loadUserByUserId(String userId) throws UsernameNotFoundException, DataAccessException {
UserDetails userDetails = userDetailsService.loadUserByUsername(userId);
return (SocialUserDetails) userDetails;
}