I'm developing a system with 3 smaller projects, which are as follows:
a client
a ressource server
a authentication server
The authentication server has a register and a login page. The resource server is secured by the authentication server.
From the client I want to access the resource via REST API. The client is calling the resource server via OAuth2RestTemplate from Spring to access the resource. I managed to access the resource, after I authenticated myself.
Now to the problem. At the client I need to know the current user to display the the username and enabling the user to change his profile.
I tried to access the principal of the user via spring security with
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
but it just returned null.
So my question is: Is there a way to get the current logged in user with the OAuth2RestTemplate?
Edit:
So I decided to change the plan to implement a link in my authentication server, which returns the user information. The problem is, when I want to talk the authentication server via OAuth2RestTemplatethe authentication server just returns the login page. Everything works fine, when I call the page from browser or when I want to talk to the resource server via OAuth2RestTemplate.
Set a TokenEnhancer to your AuthorizationServerEndpointsConfigurer in Authorization server. You can add User information to the token as additional info map.
Here is a sample implementation of a custom TokenEnhancer,
public class CustomTokenEnhancer implements TokenEnhancer {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
final Map<String, Object> additionalInfo = new HashMap<String, Object>();
UserDetails user = (UserDetails) authentication.getPrincipal();
additionalInfo.put("<custom_user_info>", user.getUsername());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}
In your Authorization server,
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.tokenEnhancer(new CustomTokenEnhancer());
}
Add authentication object to security context holder after successfull authentication, by overriding method of class AbstractAuthenticationProcessingFilter
public void successfulAuthentication(
HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authentication) throws IOException, ServletException
{
// Add the authentication to the Security context
SecurityContextHolder
.getContext()
.setAuthentication(authentication);
}
Related
So I have an Angular frontend application, and a Spring backend. Currently, it seems that I have a cookie of JSessionId on my application (which I receive only on login, and not on register, for whatever reason)
(cookies)
I assume it sends those cookies back to the server. (though that's only an assumption)
Now, when I am making a request to the protected server, the only thing I get is this "Please login" popup.
Login popup
When I log in, my UserService logs a user with such details:
UsernamePasswordAuthenticationToken [Principal=User(userId=1, name=Maksym Riabov, username=MRiabov, password={bcrypt}$2a$10$W0XJRQdfxV5XXORkr2bTluIHvFetIVBzmVp51l39T5zLCQk12RV1i, company=null, enabled=true), Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[ADMIN]]
And what I've noticed is that the sessionId is null there. Why could that be?
To answer some of the questions forward:
Yes, I've pasted {withCredentials: true} to every request. (specific to Angular)
Yes, I've read documentation - I've even tried pasting all the code from it and it seems that it didn't work.
My login controller:
#GetMapping("/login")
public ResponseEntity<String> login() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return ResponseEntity.ok("123123");
}
#PostMapping("/register")
public ResponseEntity<Map<String, String>> register(#RequestBody UserRegisterDto userDto) {
//todo check if name taken
User user = userMapper.toEntity2(userDto);
user.setPassword(passwordEncoder.encode(user.getPassword()));
user.setEnabled(true);
//todo remove
Authority authority = authorityRepository.save(new Authority("ADMIN"));
user.setAuthorities(Set.of(authority));
//todo REMOVE!!!!
User savedUser = userRepository.save(user);
System.out.println("registration works!");
return ResponseEntity.ok(Map.of("result",authority.getAuthority().getAuthority()));
}
Now, I am sending a request to the backend (which puts the popup above) like this one:
#PreAuthorize("hasRole('ADMIN')")
#GetMapping("/create")
public ResponseEntity<OnboardingPathDto> createOnboardingPath() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
// erased a bit of code here
return ResponseEntity.ok().build();
And as you see I have a method security, which throws the request for auth.
And, the cherry at the top:
#Component
#EnableWebSecurity
#EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)
#RequiredArgsConstructor
public class SecurityConfig {
#Bean
public SecurityFilterChain filterChain(HttpSecurity http, UserDetailsService userDetailsService) throws Exception {
http
.csrf().disable().cors().disable()
.authorizeHttpRequests()
.anyRequest().permitAll() //todo this is unsafe
.and().sessionManagement(session -> session.
sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.maximumSessions(1))//to force only one session per user
//here I tried sessionManagement to do something, but did it do something?
.rememberMe((rememberMe) -> rememberMe.userDetailsService(userDetailsService))
.httpBasic();
return http.build();
}
#Bean
public AuthenticationManager authenticationManager(DaoAuthenticationProvider daoAuthenticationProvider) throws Exception {
return new ProviderManager(daoAuthenticationProvider);
}
#Bean
public DaoAuthenticationProvider prov(PasswordEncoder passwordEncoder, UserDetailsService userDetailService) throws Exception {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
daoAuthenticationProvider.setUserDetailsService(userDetailService);
return daoAuthenticationProvider;
}
#Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
#Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {//to force only one session per user
return new HttpSessionEventPublisher();
}
I've read through the Spring Security documentation far and wide, and even have taken a course in it, but I still couldn't manage to get it working.
So, what I'm struggling with:
Why can't Spring authenticate through the session even though it is configured to do so? Where is my error?
Edit: I assume that sending the session directly into Angular (in REST, not in cookie) is really unsafe, right? I currently rely on cookies.
Edit 2: ffs, I'm sick of it, I'm just going to do oauth2 authentication.
Edit: I assume that sending the session directly into Angular (in REST, not in cookie) is really unsafe, right? I currently rely on cookies.
You are right, this is a bad idea. For sessions in an application running in a browser, only use cookies with those two flag raised (value=true):
secure (exchanged only over https)
http-only (hidden from Javascript).
This means that cookies should not be accessible to the Angular code but automatically set by the browser before sending requests to the backend.
You should also implement CSRF protection (which is the default in spring-security).
Edit 2: ffs, I'm sick of it, I'm just going to do oauth2 authentication.
Good idea. This is much better for security, user experience (SSO), and developper experience: most OIDC providers, either on premise (like Keycloak), or in the cloud (like Auth0, Cognito, and many others), already provide with login forms (including Multi Factor Authentication), user registration, profile edition, administration screens (like clients declaration, user roles assignement, etc.). For that:
configure your Spring REST API as a resource-server. I have written tutorials for this there
configure your Angular app either as:
an OAuth2 client. My favorite certified lib for Angular is angular-auth-oidc-client
a BFF client. Backend For Frontend is a pattern where a server-side middleware serves as the only OAuth2 client to hide tokens from the browser. Angular app won't be OAuth2: it will be secured with sessions (haha! your devil is back ;-), the middleware (something like spring-cloud-gateway with tokenRelay filter) will keep this session, associate tokens to it and replace sessions with tokens before forwarding requests to resource-server. Tutorial there.
I've been trying to implement OAuth2 password expiration filter and I'm unsure about what the proper way would be to do so. The idea is as follows:
User tries to login.
User gets response with a header containing token if the password is expired.
User get's redirected to password change page using that token (i.e. /password-change/{token}).
He submits his old and new passwords, it gets changed.
Some rest controller retrieves user id by that token and does the rest password changing logic.
User should be redirected back to the initial login page where he logins with his new password (if he would be logged in instantly after the password change, he could navigate through secured pages even if the password would not be changed in background due to some exception, etc.).
So... I set a custom flag in user details for password expiration because I can't use credentialsNonExpired as it gets validated in DaoAuthenticationProvider and thrown as an exception which gets processed as InvalidGrantException which doesn't give me much control. I've figured out that in order to access user details right after it's authentication my filter should be in the inner Spring Security filter chain placed after OAuth2AuthenticationProcessingFilter:
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
...
http.addFilterAfter(new PasswordExpirationFilter(), BasicAuthenticationFilter.class
}
}
Why does my filter get placed after OAuth2AuthenticationProcessingFilter while there's no BasicAuthenticationFilter in the chain? I've digged through Spring Security and OAuth2 documentation and sources and couldn't find the right answer.
If that user's password is expired my filter generates some random string and it saves it to retrieve user details later during the password change request (at least it should be):
public class PasswordExpirationFilter extends OncePerRequestFilter implements Filter, InitializingBean {
private static final String TOKEN_HEADER = ...;
private ExpiredPasswordRepository repo; // gets set in a constructor and is basically holding a concurrent map of tokens
...
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
UserDetails details = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (details.isPasswordExpired) {
String uuid = UUID.randomUUID().toString();
repo.push(uuid, details.getId());
SecurityContextHolder.clearContext();
SecurityContextHolder.getContext().setAuthentication(null);
request.getSession(false).invalidate(); // don't create a new session
response.addHeader(TOKEN_HEADER, uuid);
response.sendError(HttpStatus.SC_PRECONDITION_FAILED, "Credentials have expired");
} else {
filterChain.doFilter(request, response);
}
}
}
Do I have to revoke the OAuth token as well? It gets reused in later requests and I keep getting the last userDetails object and therefore I keep getting the same response from my filter.
Is it even the right place to do all this validation? How should one validate the password for the concrete user and not the OAuth client?
Ok, I think I resolved this issue by revoking the access token via injected TokenStore in my filter (I used BearerTokenExtractor to get the token value) which seems pretty logical in this situtation. I still had no time to figure out, why my filter gets placed after OAuth2AuthenticationProcessingFilter, though.
I'm using spring-security 3.2.6 and during testing I programmatically login my users with
public User login(User user) {
logOut();
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
user, user.getPassword(), user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(
authenticationToken);
return user;
}
doing it in that way I notice that isn't going through the AuthenticationSuccessHandler. Is there any way to achieve that?
What you are doing is trying to override the AuthenticationManager. When you want to override AuthentitionSeccessHandler then you need to
Override the AuthenticationFilter.
Authentication filter checks if the incoming request needs authentication, if Yes then calls the Authentication Manager to authenticate the request .
Authentication Manager authenticates request and add it to SecurityContextHolder and returns the Authrntication token which is dy default UsernamePasswordAuthenticationToken.
Filter then checks if there is a SuccessHandler defined. If yes then redirects the request to the AuthenticationSuccessHandler.
I am trying to add OAuth to a rest service that I am developing with Spring framework. I am using annotation based configuration and spring-boot to get it running.
I have the following class in my project:
#Configuration
#EnableWebSecurity
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecuritySettings extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password("123").authorities("ROLE_USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated()
.and().httpBasic().and().csrf().disable();
}
}
and my authorization server configuration is as follows:
#Configuration
#EnableAuthorizationServer
public static class MyAuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("web")
.authorizedGrantTypes("password")
.authorities("ROLE_CLIENT","ROLE_TRUSTED_CLIENT","ROLE_USER")
.scopes("read", "write")
.resourceIds(RESOURCE_ID);
}
}
When I make a GET request to /oauth/token/ end point I am asked to enter HTTP basic credentials. When I try to login with the admin user then the following is logged
o.s.s.o.provider.endpoint.TokenEndpoint : Handling error: NoSuchClientException, No client with requested id: admin
Entering username as web works, but I don't know the password for it. A default password is logged but it doesn't work either.
Using default security password: f23087f8-58ce-e3d-bc62-58bf0963e75c
So what is this password? Where can I find it? How can I set it?
The API you are using is from this builder class.
The token endpoint is used by client applications to request access tokens for resources. It isn't used by browser end users. OAuth2 clients are usually allocated a "client secret" which they can use to authenticate at the endpoint, generally with Basic authentication as described in the OAuth 2.0 spec.
So to answer your specific question, you would use the "secret" method on the builder API, and use the value to authenticate as the client:
clients.inMemory().withClient("web")
.authorizedGrantTypes("password")
.secret("webclientsecret")
...
Also, the "password" grant means that the client requests tokens using an end users ID and password, just to make sure that's what you actually intend. It's not related to the password issue here.
This is the OAuth access token. It is based on user login and password and used to access protected resources.
URL "/oauth/token" is used to fetch access tokens instead of available Request Token. This request is digitally signed on the basis of Request Token secret.
The Oauth protocol uses this access tokens in this way:
Application-Consumer gets Request Token.
User is redirected on the Service Provider's site and authorizes Request Token there. (If authorization is made via Http basic, then you should add request header with name "Authorization" and value "Basic EncodeBase64("name:password")", where EncodeBase64 is a function, "name" and "password" are user name and user password.
Application-Consumer exchanges Request Token on Access Token.
Application-Consumer sends authorized requests to the service's API.
You can't find additional info in OAuth 2 Developers Guide and Spring Social Reference
I hope you've got answer to your question(or get closer to it). =)
After a new user submits a 'New account' form, I want to manually log that user in so they don't have to login on the subsequent page.
The normal form login page going through the spring security interceptor works just fine.
In the new-account-form controller I am creating a UsernamePasswordAuthenticationToken and setting it in the SecurityContext manually:
SecurityContextHolder.getContext().setAuthentication(authentication);
On that same page I later check that the user is logged in with:
SecurityContextHolder.getContext().getAuthentication().getAuthorities();
This returns the authorities I set earlier in the authentication. All is well.
But when this same code is called on the very next page I load, the authentication token is just UserAnonymous.
I'm not clear why it did not keep the authentication I set on the previous request. Any thoughts?
Could it have to do with session ID's not being set up correctly?
Is there something that is possibly overwriting my authentication somehow?
Perhaps I just need another step to save the authentication?
Or is there something I need to do to declare the authentication across the whole session rather than a single request somehow?
Just looking for some thoughts that might help me see what's happening here.
I couldn't find any other full solutions so I thought I would post mine. This may be a bit of a hack, but it resolved the issue to the above problem:
#Autowired
AuthenticationServiceImpl authenticationManager;
public void login(HttpServletRequest request, String userName, String password) {
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(userName, password);
// Authenticate the user
Authentication authentication = authenticationManager.authenticate(authRequest);
SecurityContext securityContext = SecurityContextHolder.getContext();
securityContext.setAuthentication(authentication);
// Create a new session and add the security context.
HttpSession session = request.getSession(true);
session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext);
}
I had the same problem as you a while back. I can't remember the details but the following code got things working for me. This code is used within a Spring Webflow flow, hence the RequestContext and ExternalContext classes. But the part that is most relevant to you is the doAutoLogin method.
public String registerUser(UserRegistrationFormBean userRegistrationFormBean,
RequestContext requestContext,
ExternalContext externalContext) {
try {
Locale userLocale = requestContext.getExternalContext().getLocale();
this.userService.createNewUser(userRegistrationFormBean, userLocale, Constants.SYSTEM_USER_ID);
String emailAddress = userRegistrationFormBean.getChooseEmailAddressFormBean().getEmailAddress();
String password = userRegistrationFormBean.getChoosePasswordFormBean().getPassword();
doAutoLogin(emailAddress, password, (HttpServletRequest) externalContext.getNativeRequest());
return "success";
} catch (EmailAddressNotUniqueException e) {
MessageResolver messageResolvable
= new MessageBuilder().error()
.source(UserRegistrationFormBean.PROPERTYNAME_EMAIL_ADDRESS)
.code("userRegistration.emailAddress.not.unique")
.build();
requestContext.getMessageContext().addMessage(messageResolvable);
return "error";
}
}
private void doAutoLogin(String username, String password, HttpServletRequest request) {
try {
// Must be called from request filtered by Spring Security, otherwise SecurityContextHolder is not updated
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);
token.setDetails(new WebAuthenticationDetails(request));
Authentication authentication = this.authenticationProvider.authenticate(token);
logger.debug("Logging in with [{}]", authentication.getPrincipal());
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (Exception e) {
SecurityContextHolder.getContext().setAuthentication(null);
logger.error("Failure in autoLogin", e);
}
}
Ultimately figured out the root of the problem.
When I create the security context manually no session object is created. Only when the request finishes processing does the Spring Security mechanism realize that the session object is null (when it tries to store the security context to the session after the request has been processed).
At the end of the request Spring Security creates a new session object and session ID. However this new session ID never makes it to the browser because it occurs at the end of the request, after the response to the browser has been made. This causes the new session ID (and hence the Security context containing my manually logged on user) to be lost when the next request contains the previous session ID.
Turn on debug logging to get a better picture of what is going on.
You can tell if the session cookies are being set by using a browser-side debugger to look at the headers returned in HTTP responses. (There are other ways too.)
One possibility is that SpringSecurity is setting secure session cookies, and your next page requested has an "http" URL instead of an "https" URL. (The browser won't send a secure cookie for an "http" URL.)
The new filtering feature in Servlet 2.4 basically alleviates the restriction that filters can only operate in the request flow before and after the actual request processing by the application server. Instead, Servlet 2.4 filters can now interact with the request dispatcher at every dispatch point. This means that when a Web resource forwards a request to another resource (for instance, a servlet forwarding the request to a JSP page in the same application), a filter can be operating before the request is handled by the targeted resource. It also means that should a Web resource include the output or function from other Web resources (for instance, a JSP page including the output from multiple other JSP pages), Servlet 2.4 filters can work before and after each of the included resources. .
To turn on that feature you need:
web.xml
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/<strike>*</strike></url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
RegistrationController
return "forward:/login?j_username=" + registrationModel.getUserEmail()
+ "&j_password=" + registrationModel.getPassword();
I was trying to test an extjs application and after sucessfully setting a testingAuthenticationToken this suddenly stopped working with no obvious cause.
I couldn't get the above answers to work so my solution was to skip out this bit of spring in the test environment. I introduced a seam around spring like this:
public class SpringUserAccessor implements UserAccessor
{
#Override
public User getUser()
{
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
return (User) authentication.getPrincipal();
}
}
User is a custom type here.
I'm then wrapping it in a class which just has an option for the test code to switch spring out.
public class CurrentUserAccessor
{
private static UserAccessor _accessor;
public CurrentUserAccessor()
{
_accessor = new SpringUserAccessor();
}
public User getUser()
{
return _accessor.getUser();
}
public static void UseTestingAccessor(User user)
{
_accessor = new TestUserAccessor(user);
}
}
The test version just looks like this:
public class TestUserAccessor implements UserAccessor
{
private static User _user;
public TestUserAccessor(User user)
{
_user = user;
}
#Override
public User getUser()
{
return _user;
}
}
In the calling code I'm still using a proper user loaded from the database:
User user = (User) _userService.loadUserByUsername(username);
CurrentUserAccessor.UseTestingAccessor(user);
Obviously this wont be suitable if you actually need to use the security but I'm running with a no-security setup for the testing deployment. I thought someone else might run into a similar situation. This is a pattern I've used for mocking out static dependencies before. The other alternative is you can maintain the staticness of the wrapper class but I prefer this one as the dependencies of the code are more explicit since you have to pass CurrentUserAccessor into classes where it is required.