In our microservice each authenticated Spring Security user has an associated application-specific data structure.
when thinking on how can we easily cache this data together with the user, we thought it would be good if it could have been done similar to this:
add the cached data to the in-memory-authentication when creating the users:
public void configureGlobal(AuthenticationManagerBuilder auth) {
auth.inMemoryAuthentication().withUser("user").password("123").roles("ROLE");
auth.inMemoryAuthentication().withUser("user").cache(appDate);
...
}
pull the data in #RestController methods:
#RequestMapping("/foo")
public void foo() {
User user = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Object details = SecurityContextHolder.getContext().getAuthentication().getDetails();
Object cachedAppDate= SecurityContextHolder.getContext().getAuthentication().getCachedData();
}
Obviously the method in bold are a wish-list and do not exist.
Any advice on how to do this easily with the existing Spring Security framework?
thanks!
If you can get your cached data using only user name (no password required) you can use implement org.springframework.security.core.userdetails.UserDetailsService, it has only one method - UserDetails loadUserByUsername(String username), and return required data as custom UserDetail object. After implement interface, just pass it as UserDetailService for AuthenticationManagerBuilder.
If you need password to get that cached data, things got a bit complicated.
You should create your own AuthenticationProvider and put cached data in Principal or UserDetails. For example code for set additional data in Principal:
public class MyProvider implements org.springframework.security.authentication.AuthenticationProvider {
#Override
public boolean supports(Class<? extends Object> authentication) {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
... // check login/password
Object cachedAppDate = "i'm cached data!";
MyUser user = new MyUser(token, cachedData);
UsernamePasswordAuthenticationToken output = new UsernamePasswordAuthenticationToken(user, authentication.getCredentials(), user.getAuthorities());
output.setDetails(authentication.getDetails());
return output;
}
}
public static class MyUser extends org.springframework.security.core.userdetails.User {
private final Object cachedData;
public User(UsernamePasswordAuthenticationToken token, Object cachedData) {
super(token.getName(), "", token.getAuthorities());
this.cachedData = cachedData;
}
public Object getCachedData() {
return this.cachedData;
}
}
and access cached data as ((MyUser)SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getCachedData();
Related
My use case is that I'm using Spring Security 5.2's Oauth2 login, but would like my database user class to be available alongside the Oauth2AuthenticationToken within the Authentication. This is so that I have my database user class cached by the SecurityContextHolder.
In Pseudocode:
A user logs in using Google or Github Oauth2
My app finds (or creates) the database user with the information returned
My app saves to the SecurityContextHolder a custom Authentication wrapper that wraps both the Oauth2AuthenticationToken and the database User class
On subsequent requests, the custom Authentication wrapper is available to controller methods
Here are my attempts at a wrapper:
class MyAuthenticationWrapper implements Authentication {
public MyAuthenticationWrapper(User user, Authentication underlyingAuth1) {
this.user = user;
this.underlyingAuth = underlyingAuth1;
}
private final User user;
private final Authentication underlyingAuth;
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return underlyingAuth.getAuthorities();
}
#Override
public void setAuthenticated(boolean isAuthenticated) {
underlyingAuth.setAuthenticated(isAuthenticated);
}
#Override
public String getName() {
return underlyingAuth.getName();
}
#Override
public Object getCredentials() {
return underlyingAuth.getCredentials();
}
#Override
public Object getPrincipal() {
return underlyingAuth.getPrincipal();
}
#Override
public boolean isAuthenticated() {
return underlyingAuth.isAuthenticated();
}
#Override
public Object getDetails() {
return underlyingAuth.getDetails();
}
public User getUser() {
return user;
}
}
And a custom Oauth2AuthenticationFilter:
#Component
class CustomLoginAuthenticationFilter extends OAuth2LoginAuthenticationFilter {
#Autowired
private UserDAO userDAO;
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
Authentication auth = super.attemptAuthentication(request, response);
if (auth instanceof OAuth2AuthenticationToken) {
switch (((OAuth2AuthenticationToken) auth).authorizedClientRegistrationId) {
case "google":
Optional<User> user = userDAO.findByEmail(username);
if (!user.isPresent()) {
throw new NotFoundException("!");
}
return MyAuthenticationWrapper(auth, user.get());
}
}
return auth;
}
}
I haven't had success getting this approach to work, and I'm left wondering if this is the right approach at all.
Is there another, more idiomatic approach to combining database user data with Oauth2 user data in Spring security?
Perhaps looking into OAuth2UserService might help. It gets invoked after successfully obtaining the OAuth token. This is how it would work:
A user logs in using Google or Github Oauth2
No need to add anything. Let the default filters take care of that.
My app finds (or creates) the database user with the information returned
Create your own OAuth2UserService as a bean (it'll get picked up automatically) that takes care of dealing with the database:
#Component
public class CustomService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
#Override
public OAuth2User loadUser(OAuth2UserRequest userRequest)
throws OAuth2AuthenticationException {
// ... DB logic goes here
}
}
In loadUser(...), the OAuth2UserRequest gives you access to the corresponding ClientRegistration and the OAuth2AccessToken, which you can then use to query or update the database.
My app saves to the SecurityContextHolder a custom Authentication wrapper that wraps both the Oauth2AuthenticationToken and the database User class
No need to deal with a wrapper! The custom OAuth2User you construct from information from the database will be the Principal in the OAuth2LoginAuthenticationToken, which ends up being the Authentication, so it'll be available to your application. Since you're not dealing with the Authentication yourself, you wouldn't have to worry about saving it in the SecurityContextHolder.
On subsequent requests, the custom Authentication wrapper is available to controller methods
Your Authentication will be of type OAuth2LoginAuthenticationToken. You can get your custom OAuth2User like this:
OAuth2LoginAuthenticationToken auth = //...
OAuth2User user = auth.getPrincipal();
For more info on the core classes you're dealing with, these might be helpful:
OAuth2LoginAuthenticationFilter
OAuth2LoginAuthenticationProvider
OAuth2UserRequest
OAuth2LoginAuthenticationToken
For out-of-the-box implementations of OAuth2UserService, check out:
DefaultOAuth2UserService
CustomUserTypesOAuth2UserService
I am currently implementing a SAML SSO solution in my application where in my SAMLUserDetailsService, I am loading my user
#Service
public class SAMLUserDetailsServiceImpl implements SAMLUserDetailsService {
#Override
public Object loadUserBySAML(SAMLCredential credential) throws UsernameNotFoundException {
return new User(credential.getNameID().getValue());
}
}
I am then using a SavedRequestAwareAuthenticationSuccessHandler to redirect user to a landing controller upon successful authentication.
#Bean
public AuthenticationSuccessHandler successRedirectHandler() {
SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler =
new SavedRequestAwareAuthenticationSuccessHandler();
successRedirectHandler.setDefaultTargetUrl("/landing");
return successRedirectHandler;
}
Controller:
#RequestMapping("/landing")
public ResponseEntity landing(User user) {
return ResponseEntity.ok(user.getLoginName());
}
Is there a way to pass the User object to my controller. I noticed that this is usually done using a HandlerMethodArgumentResolver but since my application is stateless and does not use sessions, is there a way to achieve this using another way please?
You don't need injection for this. Use following instead:
SecurityContextHolder.getContext().getAuthentication().getName()
or
SecurityContextHolder.getContext().getAuthentication().getPrincipal()
In the latter case check the type of what getPrincipal() returned. It can be String, it can be UserDetails. If latter, cast it to UserDetails and call getUsername().
I am developing an Spring boot app following its oauth stantards. I was wondering if it is possible to catch some event just before the redirection to the client with the valid token.
I need this to include some extra info in the response. That would be possible? Thank you
In order to add add extra info on the token a better and more standard way is use the TokenEnhancer. It is an interface that give you the possibility of enhancing an access token before it is stored. I provide you a skeleton example below:
#Configuration
#EnableAuthorizationServer
class SecurityOAuth2AutorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.....
.tokenEnhancer(tokenEnhancer())
.approvalStoreDisabled();
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
....
}
#Bean
public TokenEnhancer tokenEnhancer(){
return new YourTokenEnhancer ();
}
}
class YourTokenEnhancer implements TokenEnhancer {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
User user = (User) authentication.getPrincipal();
final Map<String, Object> additionalInfo = new HashMap<>();
additionalInfo.put("yourAdditionalKey", "yourAdditionalValue");
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}
Update
considering your message if you want add info in an header you can use an interceptor and bind to the org.springframework.security.oauth2.provider.endpoint.TokenEndpoint class that is the class that you invoke for retrieve the token and add hear the your extra information.
However I discourage this approach in favor of a more standard way and consider to use the TokenEnhancer that is the standard way for add extra info on your token.
Update
considering the comments I can suggest to implements your audit logic with an aspect in your authentication server the aspect can be like below:
#Aspect
#Component
class AuditLogger {
#AfterReturning("execution(* org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken(..))")
public void aspect(JoinPoint joinPoint) {
Map<String, String> params = (Map<String, String>) joinPoint.getArgs()[1];
System.out.println("success");
System.out.println(params);
// your audit logic in case of successful login
}
#AfterThrowing(value = "execution(* org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken(..))", throwing = "error")
public void error(JoinPoint joinPoint, Exception error) {
System.out.println(error);
Map<String, String> params = (Map<String, String>) joinPoint.getArgs()[1];
System.out.println("error");
System.out.println(params);
// your audit logic in case of failure login
}
}
I suggest to use an aspect instead of implements custom components of spring security because: first of all, audit is a cross cutting concern and an aspect is one of the best solution to achieve this and then, because customize Spring Security, especially for Oauth2, is a challeng and i do not advice it, in my experience is too complex and the effort do not repay the effort, the protocol is very complex and fill all use cases is a challenge. With an aspect that do the audit for you in he correct point is the best option for you.
I built an aspect that do audit on the TokenEndpoint.postAccessToken method, that is the code of Spring security oAuht2 that generate the token
The code of your interest in the Spring framework is below:
#FrameworkEndpoint
public class TokenEndpoint extends AbstractEndpoint {
....
#RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, #RequestParam
Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
if (!(principal instanceof Authentication)) {
throw new InsufficientAuthenticationException(
"There is no client authentication. Try adding an appropriate authentication filter.");
}
String clientId = getClientId(principal);
ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);
TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);
if (clientId != null && !clientId.equals("")) {
// Only validate the client details if a client authenticated during this
// request.
if (!clientId.equals(tokenRequest.getClientId())) {
// double check to make sure that the client ID in the token request is the same as that in the
// authenticated client
throw new InvalidClientException("Given client ID does not match authenticated client");
}
}
if (authenticatedClient != null) {
oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
}
if (!StringUtils.hasText(tokenRequest.getGrantType())) {
throw new InvalidRequestException("Missing grant type");
}
if (tokenRequest.getGrantType().equals("implicit")) {
throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
}
if (isAuthCodeRequest(parameters)) {
// The scope was requested or determined during the authorization step
if (!tokenRequest.getScope().isEmpty()) {
logger.debug("Clearing scope of incoming token request");
tokenRequest.setScope(Collections.<String> emptySet());
}
}
if (isRefreshTokenRequest(parameters)) {
// A refresh token has its own default scopes, so we should ignore any added by the factory here.
tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));
}
OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
if (token == null) {
throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
}
return getResponse(token);
}
....
}
I hope that it can help you
My Goal - To send message to single user if possible without using spring security
I want to input a username from user and set it as username in spring security so that I can use method convertAndSendToUser. I searched on the net and found two approaches
Using DefaultHandshakeHandler to set username but this way I am unable to retrieve user input from the page and use it in determineUser method
I have tried using following piece of code
Authentication request = new UsernamePasswordAuthenticationToken("xyz", null);
SecurityContextHolder.getContext().setAuthentication(request);
But it is not working as it is just changing the username for that method and then it resets the username.
If possible is there any approach with which I can send message to single user without using spring security. Thanks in advance
P.S. I am a newbee.
You can use your first approach to set the Username. First you need add the interceptor to your StompEndpointRegistry class and after that you can determine User from the attributes Map and return the Principal.
Below is the Code:
HttpSessionHandshakeInterceptor is Used for Intercepting the Http attributes and provide them in the DefaultHandshakeHandler class
#Configuration
#EnableWebSocketMessageBroker
#EnableWebMvc
#Controller
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app","/user");
}
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/chat")
//Method .addInterceptors for enabling interceptor
.addInterceptors(new HttpSessionHandshakeInterceptor())
.setHandshakeHandler(new MyHandler())
.withSockJS();
}
class MyHandler extends DefaultHandshakeHandler{
#Override
protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler,
Map<String, Object> attributes) {
//Get the Username object which you have saved as session objects
String name = (String)attributes.get("name");
//Return the User
return new UsernamePasswordAuthenticationToken(name, null);
}
}
}
I'm using Spring Security to handle user authentication for my Spring MVC web app. I'm able to get the username from the Authentication object, but my username is the email address, and I want to be able to show the user's actual name in my header.
So I have my custom User class:
class Users{
String name;
String email;
String password;
// getters and setters
}
I thought about using an aop scoped proxy to set the User in the session, as explained in this blog: http://richardchesterwood.blogspot.co.uk/2011/03/using-sessions-in-spring-mvc-including.html . The problem I faced using this approach is that the AuthenticationSuccessHandler is actually a Service and should be stateless. So Spring doesn't autowire a Users object for me in the Service.
So I created a Service method that would get the username (or email) from the Authentication object and return my Users object. This I can use in my Controllers.
#Service
#Transactional
public class UserServiceImpl implements UserService {
#Override
public Users getCurrentUser() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User userD = (User)auth.getPrincipal();
Users currentUser = getUserByEmail(userD.getUsername());
return currentUser;
}
}
So is there a way that I can call this Service method from JSTL to get the user's full name, which I can display in my header?
Am also open to suggestions for a better way to implement this.
EDIT:
In my earlier approach using the AuthenticationSuccessHandler, my code goes like this:
#Service("userDetailsService")
#Transactional
public class UserAuthenticationServiceImpl implements AuthenticationSuccessHandler {
#Autowired
Users currentUser;
#Override
public void onAuthenticationSuccess(HttpServletRequest hsr, HttpServletResponse hsr1, Authentication a) throws IOException, ServletException {
User user = (User) a.getPrincipal();
Users user1 = userDao.getUserByEmail(user.getUsername());
currentUser.setName(user1.getName());
currentUser.setUserRoles(user1.getUserRoles());
//currentUser = user1;
}
}
And in my spring-servlet.xml file, I have this:
<bean id="currentUser" class="com.foo.bean.Users" scope="session">
<!-- this next element effects the proxying of the surrounding bean -->
<aop:scoped-proxy/>
</bean>
The problem I'm facing here is that Spring isn't autowiring my currentUser object because the Service isn't in the session scope.
If the only thing you need is the full name just use an AuthenticationSuccessHandler to retrieve the user and add the name to the session (or the full user if you need more then that).
#Override
public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse res, Authentication auth) throws IOException, ServletException {
User user = (User) auth.getPrincipal();
Users user1 = userDao.getUserByEmail(user.getUsername());
WebUtils.setSessionAttribute(req, "currentUser" user1);
}
Then in your JSP the only thing you need is ${currentUser.username}.
Although I wouldn't suggest stuffing the full user in the session I would suggest just adding the information needed.
WebUtils.setSessionAttribute(req, "currentUsername" user1.getUsername());
Then in your JSP ${currentUsername} saves you a lot of serialization overhead of the session.