I want to redirect users to a different page after login depending on what type of user they are, and if they're on a mobile device. My project uses Spring MVC and Spring Security. To get the redirect logic working, I use a Spring Security AuthenticationSuccessHandler to identify the type of user post-login and direct them to the correct page.
I was hoping to use Spring Mobile to check at this point whether they're on a mobile device, and if so send them to the mobile version of the URL. However, it seems that interceptors such as DeviceResolverHandlerInterceptor get executed after the Spring Security filters, and so the resolution hasn't happened at the point I need it to.
At the moment, the only solution I can see to this is to disable the interceptor and instead directly use LiteDeviceResolver's resolveDevice() method. This feels a little bit hacky, but I can't see another obvious choice.
Is there any better way to configure this people are aware of?
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Bean
public DeviceResolverHandlerInterceptor deviceResolverHandlerInterceptor() {
return new DeviceResolverHandlerInterceptor();
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(deviceResolverHandlerInterceptor()).order(Ordered.HIGHEST_PRECEDENCE);
}
}
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
boolean isMobile = DeviceUtils.getCurrentDevice(request) != null
&& DeviceUtils.getCurrentDevice(request).isMobile();
...
Related
I would like to access the http request, specifically auth header in AuthenticationManager.authenticate() context.
Requirement is to authenticate a custom token. There is an external library which does that and so I don't have the luxury to read out principal from the token. Hence, in the custom filter, I am returning the full token in the getPreAuthenticatedPrincipal() method. This seems borderline incorrect and I would like to not pass the token pretending it to be principal.
Is there any way I can get it without violating any framework constraints?
Or is there a better way to handle the scenario which I'm trying to achieve?
Here's the config class:
#Configuration
#EnableWebSecurity(debug = true)
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity httpSecurity) throws Exception{
CustomTokenFilter customTokenFilter = new CustomTokenFilter();
customTokenFilter.setAuthenticationManager(new CustomAuthenticationMgr());
httpSecurity
// csrf etc etc
.addFilter(customTokenFilter)
.authorizeRequests()
.mvcMatchers("/users/**")
.authenticated()
.and()
.authorizeRequests()
.mvcMatchers("/other-api/**")
.permitAll()
.and()
.httpBasic();
}
Here's the custom token filter class:
public class CustomTokenFilter extends AbstractPreAuthenticatedProcessingFilter {
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
String authorization = request.getHeader("authorization");
if(authorization.indexOf("Custom") == 0){
return Map.of("Custom",authorization.split(" ")[1]);
}
return null;
}
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
return "";
}
}
And finally, the custom authentication manager class:
public class CustomAuthenticationMgr implements AuthenticationManager {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Map<String,String> map = (Map) authentication.getPrincipal();
String token = map.get("Custom");
// Custom validation - checking length here just to simplify
if(token.length() > 0)
authentication.setAuthenticated(true);
return authentication;
}
}
Version: Spring Boot 2.6.7 (transitive: spring-core 5.3.19)
Constraints: Cannot upgrade to other versions at the moment
Thanks in advance!
You're right, this isn't a good way to do it. (It's great you noticed -- too few people care whether their code is idiomatic.)
A better way would be to start by writing your own filter that actually just... does the authentication. You can extend OncePerRequestFilter rather than something more specific. That's what Spring Security itself does, both for basic authentication (BasicAuthenticationFilter) and for OAuth bearer tokens (BearerTokenAuthenticationFilter). You may want to take a careful look at the code for BearerTokenAuthenticationFilter since the problem it solves is very similar to yours. (I wouldn't extend it, though, since it's very clearly intended to do OAuth specifically. I wouldn't straight up copy the code either -- it's fairly simple as Spring Security filters go but probably still does more than you need. Try to understand the code instead; that will help a lot with your understanding of Spring Security in general.)
Okay, so you have a filter which looks a lot like BearerTokenAuthenticationFilter. That is, it contains an AuthenticationManager and its doFilter method consists of extracting the token from the request, passing that into the AuthenticationManager and then doing some SecurityContext-related stuff. Except, problem: AuthenticationManager.authenticate() expects an Authentication, not a String, and the token is a String.
The solution is to write a wrapper object for your token which implements Authentication. You can do this a couple of ways. Personally, what I'd do is use two classes: one which you pass into AuthenticationManager.authenticate(), and one which you get back. So we have, say, CustomTokenAuthenticationRequest implements Authentication and CustomTokenAuthentication implements Authentication. Both are immutable.
CustomTokenAuthenticationRequest basically just contains the token; its isAuthenticated() is return false, its getPrincipal() returns the token and its getCredentials() also returns the token. This is essentially what Spring Security itself does with BearerTokenAuthenticationToken.
CustomTokenAuthentication, on the other hand, probably contains a UserDetails of some sort; its isAuthenticated() is return true, its getName() is a username or user id or something, etc.
Now you need to teach the AuthenticationManager to authenticate CustomTokenAuthenticationRequests. The way to do this isn't to implement AuthenticationManager, it's to implement AuthenticationProvider. So you write a class that looks roughly like
public class CustomTokenAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication a) {
String token = ((CustomTokenAuthenticationRequest) a).getToken();
if (/* the token is valid */) {
CustomTokenAuthentication returnValue = // whatever you want it to be
return returnValue;
}
throw new BadCredentialsException("Invalid token");
}
#Override
public boolean supports(Class<?> authClass) {
return authClass == CustomTokenAuthenticationRequest.class;
}
}
Finally, wire it all up. Add the authentication provider to your HttpSecurity using its authenticationProvider() method. (If you do this, and you don't change the default authentication manager configuration, authenticationProvider() results in your authentication provider getting added to an AuthenticationManager which Spring Security configures for you -- an instance of ProviderManager.) Add the filter using addFilterAt(BasicAuthenticationFilter.class). Also, don't call httpBasic() because this adds a BasicAuthenticationFilter which I am guessing you don't want. Or maybe you want basic authentication and also your custom token authentication? But you didn't say that. If you do want both, you'll want to add your filter with addFilterBefore or addFilterAfter, and you need to think about ordering. Generally filter ordering is important in Spring Security.
I glossed over a lot of stuff here, barely gave you any code, and still wrote something of blog post length. Spring Security is very complex, and the thing you're trying to do isn't easily done in an idiomatic manner if you don't have much experience. I highly recommend just reading the Spring Security reference documentation from start to finish before you try implementing any of my suggestions. You'll also need to read quite a lot of Javadoc and tutorials and/or framework code. If there's something specific you want to follow up on I might respond to a comment, but I don't promise it; I had to do some research for this answer and have already spent more time on it than I planned to.
you should look spring-security-lambda-dsl,add filter,add auth provider
I am spring newbie
I've implemented OAuth2 implicit flow using spring security.
The question is how to check token validity? I've found oauth/check_token endpoint but first I wasn't able to reach it. Then I've made the following change:
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception
{
oauthServer.checkTokenAccess("permitAll()");
}
After the configuration I can use check_token endpoint but I wonder if it is correct to use permitAll on the endpoint. I've tried to change it to isAuthenticated but in that case I am not able to reach the endpoint because I don't store client_secret on my frontend app.
Should I continue use permitAll or there is better way?
You should check access while using oAuth.
Try below code if works,
#Override
public void configure(
AuthorizationServerSecurityConfigurer oauthServer)
throws Exception {
oauthServer
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
If doesn't, please share your security related snippets. Happy coding :)
I've configured a custom authentication provider, a success handler and a failure handler in Spring Security (v4.0.1). When using the default ones, after displaying the login page, the user was redirected to the previously requested url. However, I lost that behaviour when implementing my own ones, so I'm trying to recover it.
Basically, right now, I'm being redirected to the home page everytime I log in, even I've accesed the login page trying to fetch another resource (in my case /web/users). That's the configuration I have right now:
#Configuration bean (extends WebSecurityConfigurerAdapter)
#Override
protected void configure(HttpSecurity http) throws Exception {
http.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/web/logout"))
.invalidateHttpSession(true).logoutSuccessUrl("/web/login");
http.authorizeRequests()
.antMatchers("/javax.faces.resource/**")
.permitAll()
.antMatchers("/web/recovery").permitAll()
.antMatchers("/web/users").authenticated().anyRequest()
.authenticated().and().formLogin()
.failureHandler(failureHandler).loginPage("/web/login")
.loginProcessingUrl("/web/j_spring_security_check")
.successHandler(successHandler).permitAll();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth,
DataSource ds, PasswordEncoder pwdEncoder) throws Exception {
auth.authenticationProvider(authProvider);
}
The custom AuthenticationSuccessHandler
public class SystemAuthenticationSuccessHandler extends
SavedRequestAwareAuthenticationSuccessHandler {
private IUserService service;
public SystemAuthenticationSuccessHandler(IUserService service) {
this.service = service;
setDefaultTargetUrl("/web/home");
}
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
String username = request.getParameter("username");
User user = service.findByIdOrEmail(username, username);
if (user != null) {
service.saveLoginSuccess(user.getId());
}
//Call the parent method to manage the successful authentication
super.onAuthenticationSuccess(request, response, authentication);
}
Basically, the problem I have is requestCache is always returning null for current request in the SavedRequestAwareAuthenticationSuccessHandler#onAuthenticationSuccess method and that's because the HttpSessionRequestCache#saveRequest method doesn't match my incoming request before being redirected to the login page.
Specifically, the HttpSessionRequestCache Spring is using is an AndRequestMatcher which discards all my incoming requests. I want it to use the AnyRequestMatcher, but don't know how to tell Spring that.
Update
Having set a breakpoint in HttpSessionRequestCache#setRequestMatcher, that's the concrete point where Spring Security sets it:
However, I don't know how to set a custom request cache configurer for my case! Isn't there an easier way for doing it?
Update 2
I've discovered right now this issue only happens when using Firefox and not with Chrome or Internet Explorer.
It's actually a problem with my current firefox browser. Even if I remove all the navigation and cache data, it keeps happening, but not with other browsers as Chrome or IE, not even with other FF browsers.
I've searched for a solution but can't find one anywhere, at least not a current one or one that uses non-xml based Spring and Spring Security configuration.
I need to implement a handler that will be used prior to the spring logout handler. I've read plenty of articles about the LogoutSuccessHandler but that is called after a successful logout by the Logout Filter and I need to access user data that is stored in the users session to perform some database entries, site logout info, etc. This session is lost once spring logs out the user so it has to be before that.
I've tried creating my own custom logout class and defined it in my application configuration class like this:
#Bean
public CustomLogoutHandler customLogoutHandler() {
return new CustomLogoutHandler();
}
and my class extends the LogoutHandler like the spring documents say to do:
public class CustomLogoutHandler extends LogoutHandler {
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
// business logic here
}
}
This is still not working. I put a breakpoint in the code and it never gets picked up. Does anyone have an idea of what could be causing this or what I need to do to get it to work?
To use your own custom logout handler that implements Spring's LogoutHandler.class you need to let Spring know that you are using your own in the configuration file under the logout options using .addLogoutHandler. I think you were missing this step. In the security config file:
public class SecurityConfig extends WebSecurityConfigurerAdapter {
... // Other methods here
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.otherConfigOptions
.logout()
.addLogoutHandler(customLogoutHandler()) <- custom handler
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.otherConfigOptions....
}
}
And define the bean, I put mine in the SecurityConfig.class but I think you can put it in the web or app config class depending on how you set up your project.
#Bean
public CustomLogoutHandler customLogoutHandler() {
return new CustomLogoutHandler();
}
Then, create your CustomLogoutHandler.class, making sure to IMPLEMENT the LogoutHandler and OVERRIDE the logout method. Here you can use the Authentication class to access anything you have added to the users request scope.
public class CustomLogoutHandler implements LogoutHandler {
#Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
// business logic here
}
}
You should also take a look at this question and answer which talks about the order of custom handler mappings in Spring.
I hope this helps.
I am using spring & jersey2 to serve some rest-requests like:
#GET
#Path("/someservice")
#Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
public String getSomeStuff(...) {
login(...);
// ...
}
During a rest-request, I get an authorized user of the rest-request.
Now I need this user while updating or creating entities like:
#MappedSuperclass
public abstract class PersistentObject {
#PrePersist
#PreUpdate
public void onSaveOrUpdate() {
setCreationUser(...); // How to get the user of this session?
}
// ...
}
How can I get the current user of the rest-request there?
You can try to perform your login operation (for appropriate resource methods) in a ContainerRequestFilter and set SecurityContext:
#Provider
public class SecurityFilter implements ContainerRequestFilter {
#Override
public void filter(final ContainerRequestContext context) throws IOException {
final Principal user = login(...);
context.setSecurityContext(new SecurityContext() {
public Principal getUserPrincipal() {
return user;
}
// Other methods omitted.
});
}
}
Make sure you have jersey-spring3 module on your class-path and the Jersey-Spring integration allows you to inject SecurityContext into a Spring service:
#Service
public MySpringService implements MyService {
#Context
private SecurityContext context;
public String doStuff() {
final Principal user = context.getUserPrincipal();
// ...
}
}
You can't do this if the service, you want to use user principal in, is neither managed by Jersey nor Spring.
Spring Security might be useful to you in two ways:
It can manage authentication, (you would not need to do that login(...) call yourself, it would be done automatically by Spring Security filter chain. But you can still do it manually if you want.
Once a request has been authenticated, as long as the request is alive you can access the authenticated user from anywhere just by doing:
Authentication auth = SecurityContextHolder.getSecurityContext().getAuthentication();
// auth is an object that holds the authenticated user's data
I think you need some sort of authentication by the fact that you make a login(...) and you want to audit the user afterwards. You might not nedd an authentication form, but you do need authentication. Spring Security is not only for interactive applications, you can set up an authentication filter that does authentication based on cookies, request parameters, client certificates or whatever, all of that without user interaction.
Furthermore, Spring Security is very extensible, if you have your authentication method already implemented, integrating with Spring Security is easy. And it is also flexible: you don't need to use the security filter chain if it is too heavyweight for your use case. You can do most things manually and use just a little bit of Spring Security if you want.
I really suggest you take a deeper look at Spring docs about:
Spring Security core components overview and Spring Security authentication overview
I think with just that you will be able to get something working.