My web app is built with Spring MVC (4.2.9.RELEASE) and Spring Security (3.2.5.RELEASE). I use the loadUserByUsername method to find the user from the database.
public class MyUserDetailsServiceImpl implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
//how to get the HttpSession from within this methd?
}
....
}
How can I get the HttpSession from within this method?
In the applicationContext.xml file, I have the following for security:
<http create-session="always" use-expressions="true" request-matcher="regex" entry-point-ref="authenticationEntryPoint" >
....
</http>
There is a way to get current request:
Get the request object from RequestHolderContext and from that one get session:
public Optional<HttpServletRequest> getCurrentHttpRequest() {
return Optional.ofNullable(RequestContextHolder.getRequestAttributes())
.filter(requestAttributes -> ServletRequestAttributes.class.isAssignableFrom(requestAttributes.getClass()))
.map(requestAttributes -> ((ServletRequestAttributes) requestAttributes))
.map(ServletRequestAttributes::getRequest);
}
Use it like this:
Optional<HttpServletRequest> request = getCurrentHttpRequest();
HttpServletRequest currentRequest = request.get();
Hope this helps you
There is an answer exactly to your question at Spring Security documentation of more recent version (5.1.5 for instance). In short:
How do I access the HttpSession from a UserDetailsService?
You can’t, since the UserDetailsService has no awareness of the servlet API.
The #Atul's answer can be a good workaround but please be aware that RequestContextHolder internally relies on the current thread and thus may miss the session if called from another thread.
Before making my post, I already have a solution and it seems working, but not sure whether my solution is error-free or Spring already has a more elegant solution. Here is my solution:
I built filter as follows:
public class MyFilter implements Filter {
...
public void doFilter(ServletRequest request, ServletResponse response, FilterChain
chain) throws IOException, ServletException {
//create a thread-safe object and record the request object
}
}
Then, in the loadUserByUsername method, get the request object (thus the HTTP session) from the thread-safe object.
Comments? Please feel free let me know whether this approach has any problem.
UPDATE
MyFilter is declared in web.xml, together with Spring Security's DelegatingFilterProxy.
Related
I have a question that comes from reading a lot of examples from Spring user and oauth2 but still failing at some basic concepts, maybe someone can point me in good directions/examples or of information to read.
so I'm trying to build a rest based API application.
to access the endpoints they will be protected by a JWT token and the idea is that the token is used for SSO across several other applications.
my initial idea was to have some authentication filter interceptors that process the token so I can store whatever additional information I may need and then use that user in the actual business layer of my services.
I've implemented some AuthenticationFiler that implements Filter
and I can get the additional information after I read the token access from my token store.
Now my first problem starts in fact that most of the examples start by the login/logout page and since I'm having more of an API setup I really don't have that flow.
Second, it seems most times the way to get the user is from the principal (SecurityContextHolder.getContext().getAuthentication().getPrinciple()) something like this but my principal is always null, not sure if this is because I don't really know if this is because of the stateful vs. stateless or the sorts.
So my biggest problems are to understand how can I share user details between my security interceptor and my business layer. maybe this questions is not so much about spring per se and I have more basic knowledge missing but maybe someone can give me some pointers.
this is my authentication filter, what I want to know is how can I create a user instance the same way I can use the MDC to store the user information.
Ideally, I would like to create a user instance there and just pass it to the business layer. can I do that with an Autowire?
#Component
#Order(Ordered.LOWEST_PRECEDENCE)
public class AuthenticationFilter implements Filter {
#Autowired
TokenStore tokenStore;
#Autowired
JwtAccessTokenConverter accessTokenConverter;
#Override
public void init(FilterConfig filterConfig) {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
if (authentication instanceof OAuth2Authentication) {
OAuth2Authentication oAuth2Authentication = (OAuth2Authentication) authentication;
OAuth2AuthenticationDetails oauth2AuthenticationDetails = (OAuth2AuthenticationDetails)oAuth2Authentication.getDetails();
OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(oauth2AuthenticationDetails.getTokenValue());
Object decodedDetails = oauth2AuthenticationDetails.getDecodedDetails();
Map<String, Object> additionalInformation = oAuth2AccessToken.getAdditionalInformation();
MDC.put("sub", additionalInformation.get("sub").toString());
MDC.put("preferred_username", additionalInformation.get("preferred_username").toString());
}
}
try {
chain.doFilter(request, response);
}
finally {
MDC.remove("sub");
MDC.remove("preferred_username");
}
}
#Override
public void destroy() {
}
}
not sure if this is my miss-understanding but I think what I'm looking for is dependency injections.
somehow I wanto create a new User Bean, fill it in my filter and consumer it elsewhere.
I guess I could do a #autowire in my business layer and set it in the filter and use it in the business layer?
is this a bad pattern?
As far as I understand you architecture, you are developing this:
Rest API, which should be protected by JWT.
and you want this:
Access user information received in incoming JWT in your business layer.
If both of the above assumptions are correct, you should read about how spring is doing this in it's microservice architecture. read this for quick start:
https://dzone.com/articles/json-web-tokens-with-spring-cloud-microservices
Hope this helps.
I have been really been searching high and low for the last few days on how to do this and have finally decided to admit defeat and ask for help, please!!!
I have followed Dr Dave Syer's tutorial on Angular and Spring Security specifically the Zuul Proxy as an api gateway and using Spring Session with Redis (https://github.com/spring-guides/tut-spring-security-and-angular-js/tree/master/double#_sso_with_oauth2_angular_js_and_spring_security_part_v)
The issue I am having is that I am calling resource rest services via the gateway from an external application with the following header:
String plainCreds = "user:password";
byte[] plainCredsBytes = plainCreds.getBytes();
byte[] base64CredsBytes = Base64.getEncoder().encode(plainCredsBytes);
String base64Creds = new String(base64CredsBytes);
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Basic " + base64Creds);
to be authenticated and then routed by zuul and then the resource to have access to the authenticated session via redis.
The issue is that the session seems to only commit to redis in the gateway after the request has responded. So what is happening is that when I call a resource service with the header, I can see the successful authentication occurring in the gateway and session being created, however I am getting a 403 in the resource due to the session not being in redis after its been routed via zuul.
However if I get the error, grab the session id and add it to the header and try again it works because now my authenticated session is available for the resource project after its been routed.
Please could someone point me in the direction of how I go about getting my calls via the gateway to authenticate and route in the same request please?
Thanks
Justin
I followed Justin Taylor's posts on different pages so this is his solution. It makes me sense to have solution with source code here:
Make Spring Session commit eagerly - since spring-session v1.0 there is annotation property #EnableRedisHttpSession(redisFlushMode = RedisFlushMode.IMMEDIATE) which saves session data into Redis immediately. Documentation here.
Simple Zuul filter for adding session into current request's header:
#Component
public class SessionSavingZuulPreFilter extends ZuulFilter {
#Autowired
private SessionRepository repository;
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 0;
}
#Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
HttpSession httpSession = context.getRequest().getSession();
Session session = repository.getSession(httpSession.getId());
context.addZuulRequestHeader("Cookie", "SESSION=" + httpSession.getId());
log.info("ZuulPreFilter session proxy: {}", session.getId());
return null;
}
}
Once more - this is not my solution - credentials go to Justin Taylor.
I am so sorry about the delayed response here, one of the great things about South Africa is our great telecoms hehe, I have had no internet at home for a while and my source code for this is on my home pc.
Yes Steve is on the right track. There are two issues that you need to be resolve here:
Spring session only commits the authenticated session to redis on response to the initial incoming request. So the first step is to follow that link steve provided to ensure spring session commits to redis whenever the session changes.
Zuul doesn't propagate this newly authenticated session on the initial routing. So what you need to do is to use a zuul pre filter (lots of examples around) that gets the authenticated session id and then adds it to the zuul request to the resource behind the gateway. You will see a setter method on the zuul request to set the session id.
If you don't do this, you will need to do two calls, one to authenticate and get a valid session id which would be in redis from spring session, and then the subsequent call with your authenticated session id.
I did battle with this for a while, but when I got it working it was spot on. I extended this solution to not only work for http basic, but added in a jwt token implementation.
Hopefully this helps, as soon as I am connected at home I can post the source.
Good Luck!
Justin
My APIGateway (Zuul) is proxied by Apache Httpd and protected by Mellon module (SAML 2.0). After a successfully authentication on the identity provider, mellon module inject correctly some headers read into the SAML response, but the first request fails with a 403 status code.
I'm also using SpringSecurity, to solve the problem I'm using a simple filter added on the security filter chain that ensure the correct creation of SecurityContext:
#Component
public class MellonFilter extends OncePerRequestFilter {
private final Logger log = LoggerFactory.getLogger(MellonFilter.class);
#Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
String mellonId=req.getHeader("mellon-nameid");
if(mellonId==null||mellonId.isEmpty())
;//do filterchain
else {
UserWithRoles userWithRoles = new UserWithRoles();
userWithRoles.setUsername(mellonId);
SilUserDetails details = new SilUserDetails(userWithRoles);
SilAuthenticationPrincipal silPrincipal = null;
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("Some roles");
silPrincipal = new SilAuthenticationPrincipal(details, true, authorities);
SecurityContextHolder.clearContext();
SecurityContextHolder.getContext().setAuthentication(silPrincipal);
}
filterChain.doFilter(req,httpServletResponse);
}
#Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
if(SecurityContextHolder.getContext().getAuthentication()!=null&&SecurityContextHolder.getContext().getAuthentication() instanceof SilAuthenticationPrincipal)
return true;
return false;
}
}
Then I need a ZuulFilter to save the session (on Redis) and to propagate the actual session id:
public class ZuulSessionCookieFilter extends ZuulFilter {
private final Logger log = LoggerFactory.getLogger(ZuulSessionCookieFilter.class);
#Autowired
private SessionRepository repository;
#Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
#Override
public int filterOrder() {
return 0;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() throws ZuulException {
RequestContext context = RequestContext.getCurrentContext();
HttpSession httpSession = context.getRequest().getSession();
httpSession.setAttribute(
HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
SecurityContextHolder.getContext()
);
Session session = repository.findById(httpSession.getId());
context.addZuulRequestHeader("cookie", "SESSION=" + base64Encode(httpSession.getId()));
log.debug("ZuulPreFilter session proxy: {} and {}", session.getId(),httpSession.getId());
return null;
}
private static String base64Encode(String value) {
byte[] encodedCookieBytes = Base64.getEncoder().encode(value.getBytes());
return new String(encodedCookieBytes);
}
}
I hope this solution will be helpful to everyone.
We have an application which is using spring-security-oauth2:1.0. I was trying to change it to a newer version, spring-security-oauth2:2.0.7.RELEASE. Some classes were removed, some package structure is changed, I managed to sort out all those things and I was able to start the server without any issue. But I am facing a strange issue here.
With OAuth2 - 1.0 version, when the user logs in we used to do a GET request on /oauth/token, For example :
http://localhost:8080/echo/oauth/token?grant_type=password&client_id=ws&client_secret=secret&scope=read,write&username=john#abc.com&password=password123
and It used to work just fine.
When I try the same thing, First of all I am not able to make a GET request because of the logic in TokenEndPoint.java
private Set<HttpMethod> allowedRequestMethods = new HashSet<HttpMethod>(Arrays.asList(HttpMethod.POST));
#RequestMapping(value = "/oauth/token", method=RequestMethod.GET)
public ResponseEntity<OAuth2AccessToken> getAccessToken(Principal principal, #RequestParam
Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
if (!allowedRequestMethods.contains(HttpMethod.GET)) {
throw new HttpRequestMethodNotSupportedException("GET");
}
return postAccessToken(principal, parameters);
}
I have tried to make a POST request same as above URL, but I get InsufficientAuthenticationException with the error message
There is no client authentication. Try adding an appropriate authentication filter
This is because of the following POST request controller in TokenEndpoint.java. When I debug, I see that principal is null.
#RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, #RequestParam
Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
//principal is null here
if (!(principal instanceof Authentication)) {
throw new InsufficientAuthenticationException(
"There is no client authentication. Try adding an appropriate authentication filter.");
}
.............
}
I have an authentication filter and it worked well when I used version 1.0. This is the relevant prats of my config:
<authentication-manager xmlns="http://www.springframework.org/schema/security">
<authentication-provider user-service-ref="userDetailsService"/>
</authentication-manager>
<bean id="userDetailsService" class="com.hcl.nc.service.UserDetailsService">
<constructor-arg><ref bean="sessionFactory" /></constructor-arg>
</bean>
I always thought that the request will be authenticated by authentication-provider and goes to token-endpoint but that does not seem to be the correct flow. After debugging the application with version 2.0.7, now I really doubt my understanding about the flow.
Could somebody please explain why it worked in previous version and why it's not working now?
Do I have do to something different to get a OAuth token??
NOTE: I have already checked these questions : here, here, here. But I was not able to find the correct solution.
I don't know the previous version, but I know a bit about 2.0.7.
I suspect your problem is that your TokenEndpoint security tries to authenticate your clients against your user service.
The TokenEndpoint is protected by a BasicAuthenticationFilter. By default this filter would use an AuthenticationManager instance, which itself holds an AuthenticationProvider, which itself depends on an instance of UserDetailsService.
The trick is that this particular instance of UserDetailsService must be client based, not user based : that's why there is a ClientDetailsUserDetailsService, which adapts ClientDetailsService to UserDetailsService.
Normally all this stuff is already done by default when you use the framework's configuration classes AuthorizationServerConfigurerAdapter, #EnableAuthorizationServer, etc..
I had the same problem and my application.yml had this line:
servlet:
path: /auth
so the token address was: /auth/oauth/token
I remove the path from application.yml so the token path became:
/oauth/token
And everything works fine.
I hope this help
One of the problems of the following error, can be that authentication was not performed. I have encountered this problem with older implementation of Spring.
verify that:
TokenEndpoint -> postAccessToken method. Check if Principal is not null. If it is null it means that Basic Authroziation was not performed.
One of the solution to add filter was to use:
#Configuration
public class FilterChainInitializer extends AbstractSecurityWebApplicationInitializer {
}
More information about AbstractSecurityWebApplicationInitializer can be found in Spring docs
The problem can be because of opening all requests. You should remove it.
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/**");
}
in my case, i found this config:
security.allowFormAuthenticationForClients(); // here
then post this
http://localhost:8081/sso/oauth/token?client_id=unity-client&client_secret=unity&grant_type=authorization_code&code=Yk4Sum&redirect_uri=http://localhost:8082/sso-demo/passport/login
its works for me, try it
#Configuration
#EnableAuthorizationServer
public class Oauth2Config extends AuthorizationServerConfigurerAdapter {
private static final Logger log = LoggerFactory.getLogger(Oauth2Config.class);
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients(); // here
}
#Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception { // #formatter:off
clients.inMemory()
.withClient("unity-client")
.secret("unity")
.authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
.scopes("foo", "read", "write")
.accessTokenValiditySeconds(3600) // 1 hour
.refreshTokenValiditySeconds(2592000) // 30 days
;
} // #formatter:on
#Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
}
}
I am following this tutorial - Practical Guide to Building an API Back End with Spring Boot'. See https://www.infoq.com/minibooks/spring-boot-building-api-backend , But with the latest SpringBoot Version(2.7)
and I run into this problem:
org.springframework.security.authentication.InsufficientAuthenticationException: There is no client authentication. Try adding an appropriate authentication filter. at org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken(TokenEndpoint.java:91) ~[spring-security-oauth2-2.3.5.RELEASE.jar:na]
My solution/fix was to annotate WebSecurityGlobalConfig with #EnableWebSecurity because in the original course this annotation was missing.
So adding this annotaiton has fixed the error for me.
I have questions on the following areas: spring-session and spring-security.
Spring Session
I have a application protected with Spring Security through basic in-memory authentication as provided in the example sample.
I see spring is creating session id's even the authentication is not successful, meaning I am seeing x-auth-token in my response header as well in the Redis DB even if I don't supply basic authentication credential details.
How do we avoid creating sessions for authentication failures?
Spring Security
Want to use spring security to protect resources assuming spring session creates session only for the protected resources.
Assuming a Signin API (/signin - HTTP Post) validates (username & password) credentials against a third-party REST API .
Once the external API validates the credentials, how do I update the spring security context on the successful authentication?
Access to other secured resources with the x-auth-token needs to be validated and based on the information access to the secured resource should be provided.
Do we need to have Spring Security in this case or shall I use a basic filter and spring session? What is recommended?
Typically it would be best to break your questions into multiple StackOverflow questions since you are more likely to find someone that knows the answer to a single question than both.
How do we avoid creating sessions for authentication failures ?
By default Spring Security will save the last unauthenticated request to session so that after you authenticate it can automatically make the request again. For example, in a browser if you request example.com/a/b/c and are not authenticated, it will save example.com/a/b/c to the HttpSession and then have the user authenticate. After you are authenticated, it will automatically give you the result of example.com/a/b/c. This provides a nice user experience so that your users do not need to type the URL again.
In the case of a REST service this is not necessary since the client would remember which URL needs to be re-requested. You can prevent the saving by modifying the configuration to use a NullRequestCache as shown below:
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.requestCache()
.requestCache(new NullRequestCache())
.and()
.httpBasic();
}
You can provide custom authentication by providing your own AuthenticationProvider. For example:
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.AuthorityUtils;
public class RestAuthenticationProvider implements AuthenticationProvider {
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
String username = token.getName();
String password = (String) token.getCredentials();
// validate making REST call
boolean success = true;
// likely your REST call will return the roles for the user
String[] roles = new String[] { "ROLE_USER" };
if(!success) {
throw new BadCredentialsException("Bad credentials");
}
return new UsernamePasswordAuthenticationToken(username, null, AuthorityUtils.createAuthorityList(roles));
}
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class
.isAssignableFrom(authentication));
}
}
You can then configure your RestAuthenticationProvider using something like this:
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
#Bean
public RestAuthenticationProvider restAuthenticationProvider() {
return new RestAuthenticationProvider();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth, AuthenticationProvider provider) throws Exception {
auth
.authenticationProvider(provider);
}
}
Session IDs are getting stored in Redis even when authentication fails.
Rob's answer of setting NullRequestCache didn't work for me. That is, there were redis entries even after setting request cache to NullRequestCache. To make it work, I had to use an authentication failure handler.
http.formLogin().failureHandler(authenticationFailureHandler()).permitAll();
private AuthenticationFailureHandler authenticationFailureHandler() {
return new AuthenticationFailureHandler();
}
public class AuthenticationFailureHandler
extends SimpleUrlAuthenticationFailureHandler {
}
Note that the failure handler does nothing but extend the default handler explicitly. It returns a 401 in case of failure. If you were doing redirects, you can configure it in the handler easily.
I am using Spring 3.1.0.RC3 and JSF 2.0.
I implemented a custom spring security filter and I want to store an object in the user session in order to recover it later. Here is how I do it.
public class SpringCustomSecurityFilter extends AbstractAuthenticationProcessingFilter
{
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException,
IOException, ServletException
{
// Putting the attribute
request.getSession().setAttribute("OBJECT_STRING","hola");
// Recovering the attribute
String aux = request.getSession().getAttribute("OBJECT_STRING");
}
}
The problem is that it actually put the object in session but after when I enter into the filter again the attribute doesn't exist in session. So how can I store attribute in session with a JSF front end?
it should work, have you checked if your session is the same? You can call
request.getSession().getId()
to verify it.