I just discovered the new Spring Security 4 test annotation #WithMockUser, but I cannot have it working for my Selenium test.
The thing is, this annotation creates a mock SecurityContext, but a new one is created by the HttpSessionSecurityContextRepository because Selenium runs the test based on a real browser.
Could I somehow tell Spring to use the mock SecurityContext as the next session security context?
Thanks!
I found to way to authenticate a mock user with a new filter in the test classpath:
#Component
public class MockUserFilter extends GenericFilterBean {
#Autowired
private UserDetailsService userDetailsService;
private SecurityContext securityContext;
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
if (securityContext != null) {
SecurityContextRepository securityContextRepository = WebTestUtils.getSecurityContextRepository(request);
HttpRequestResponseHolder requestResponseHolder = new HttpRequestResponseHolder(request, response);
securityContextRepository.loadContext(requestResponseHolder);
request = requestResponseHolder.getRequest();
response = requestResponseHolder.getResponse();
securityContextRepository.saveContext(securityContext, request, response);
securityContext = null;
}
chain.doFilter(request, response);
}
public void authenticateNextRequestAs(String username) {
UserDetails principal = userDetailsService.loadUserByUsername(username);
Authentication authentication = new UsernamePasswordAuthenticationToken(principal, principal.getPassword(), principal.getAuthorities());
securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(authentication);
}
}
It is inspired from SecurityMockMvcRequestPostProcessors and WithUserDetailsSecurityContextFactory.
I could not use #WithUserDetails annotation because I run Cucumber tests, but with this filter I can mock an authentication for the next request in one line: testSecurityFilter.authenticateNextRequestAs("username");
I'm adding this answer because while the accepted answer helped me in forming a solution, I had to make some changes to get this to work. This answer also helped me in getting it working: https://stackoverflow.com/a/8336233/2688076
Here is my MockUserFilter:
#Component("MockUserFilter")
public class MockUserFilter extends GenericFilterBean {
#Autowired
private UserDetailService userDetailService;
private SecurityContext securityContext;
#Autowired
private AuthenticationProvider authenticationProvider;
public void setUserDetailService(UserDetailService userDetailService) {
this.userDetailService = userDetailService;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest servletRequest = (HttpServletRequest) request;
HttpServletResponse servletResponse = (HttpServletResponse) response;
if (securityContext != null) {
SecurityContextRepository securityContextRepository = WebTestUtils.getSecurityContextRepository(servletRequest);
HttpRequestResponseHolder requestResponseHolder = new HttpRequestResponseHolder(servletRequest, servletResponse);
securityContextRepository.loadContext(requestResponseHolder);
servletRequest = requestResponseHolder.getRequest();
servletResponse = requestResponseHolder.getResponse();
securityContextRepository.saveContext(securityContext, servletRequest, servletResponse);
securityContext = null;
}
chain.doFilter(request, response);
}
public void authenticateNextRequestAs(String username, ServletRequest request) {
UserDetails principal = userDetailService.loadUserByUsername(username);
Authentication authentication = new UsernamePasswordAuthenticationToken(principal, principal.getPassword(), principal.getAuthorities());
securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(authentication);
SecurityContextHolder.getContext().setAuthentication(authentication);
HttpSession session = ((HttpServletRequest) request).getSession(true);
session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext);
}
}
In addition to this I also had to remove my casAuthenticationFilter from the filter chain to get this working. I use a properties value to enable/disable this.
I'm relatively new to Spring and Spring security so any comments on this solution are welcome. I'm not sure how "good" or "bad" this solution is.
One thing to keep in mind is that this is a solution for local testing or testing in a secure environment, not one that you'd want in a dev environment.
Related
I have a scenario that based on the request body content, user should be allowed to access certain resource on SOAP services. I can't achieve this using antMatcher(**) because the path is same for all request.
I tried by adding a filter:
public class MyFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest r = (HttpServletRequest)request;
MyRequestWrapper req = new MyRequestWrapper(r);
String body = req.getBody();
if(body.indexOf("searchKeyOnBody")!=0){
//Need to check if user has specified role or not
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Set<String> roles = authentication.getAuthorities().stream()
.map(r -> r.getAuthority()).collect(Collectors.toSet());
boolean hasManagerRole = authentication.getAuthorities().stream()
.anyMatch(r -> r.getAuthority().equals("ROLE_MANAGER"));
if(!hasManagerRole){
throwUnauthorized(response);
return;
}
}
chain.doFilter(req, response);
}
In spring security config:
#Configuration
public class MyAppConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.**.addFilterAfter(new MyFilter (), UsernamePasswordAuthenticationFilter.class)
The problem here is Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); in filter class is null. So, I am not able to retrive the user info and it's role.
Question:
Is there any way to retrieve the user info in the filter?
Anybody have better idea for this?
I have a custom filter that is used to authenticate the user. I am always getting full authentication requried error even though I have thrown a custom exception with specific message & added exception handler as well.
Code for filter:
#Slf4j
#Component
public classTokenValidationFilter extends OncePerRequestFilter {
#Autowired
private TokenValidationHelper tokenValidationHelper;
#Override
protected void doFilterInternal(HttpServletRequest servletRequest,
HttpServletResponse servletResponse,
FilterChain filterChain) throws ServletException, IOException {
HttpServletRequest httpRequest = (HttpServletRequest)servletRequest;
HttpServletResponse httpResponse = (HttpServletResponse)servletResponse;
MultiReadRequestWrapper request = new MultiReadRequestWrapper(httpRequest);
SecurityContext context = SecurityContextHolder.getContext();
// check if already authenticated
if (context.getAuthentication() == null) {
Authentication authentication =
tokenValidationHelper.validateAndAuthenticate(request);
context.setAuthentication(authentication);
}
filterChain.doFilter(request, httpResponse);
}
}
Code for exception handler:
#ControllerAdvice
public class ExceptionHandler {
#ExceptionHandler({IrrecoverableAuthException.class})
#ResponseBody
#ResponseStatus(HttpStatus.UNAUTHORIZED)
public RegistrationErrorResponse handleInternalServerException(IrrecoverableAuthException exception) {
return getErrorResponse(exception , Category.Error exception.getMessage());
}
}
But still getting wrong message
"Full authentication access is required to access this resource"
Exception handler won't be invoked from within the filter. You can use HttpServletResponse from within the filter and write your error response manually as follows:
protected void onFailedAuthentication(
HttpServletRequest request,
HttpServletResponse response,
IrrecoverableAuthException failed) {
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(failed.getStatus().getStatusCode());
try (OutputStream out = response.getOutputStream()) {
out.write(MAPPER.writeValueAsBytes(getErrorResponse())); // build the required response here
out.flush();
} catch (IOException e) {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
}
}
Call this method from your filter
#Slf4j
#Component
public classTokenValidationFilter extends OncePerRequestFilter {
#Autowired
private TokenValidationHelper tokenValidationHelper;
#Override
protected void doFilterInternal(HttpServletRequest servletRequest,
HttpServletResponse servletResponse,
FilterChain filterChain) throws ServletException, IOException {
HttpServletRequest httpRequest = (HttpServletRequest)servletRequest;
HttpServletResponse httpResponse = (HttpServletResponse)servletResponse;
MultiReadRequestWrapper request = new MultiReadRequestWrapper(httpRequest);
SecurityContext context = SecurityContextHolder.getContext();
// check if already authenticated
if (context.getAuthentication() == null) {
try {
Authentication authentication =
tokenValidationHelper.validateAndAuthenticate(request);
context.setAuthentication(authentication);
} catch(IrrecoverableAuthException ex) {
onFailedAuthentication(httpRequest, httpResponse, ex);
}
}
filterChain.doFilter(request, httpResponse);
}
}
I'm using Spring Boot + Spring Security and trying to implement an AuthenticationSuccessHandler so that the app can save a last login timestamp to the database for the user.
Below is my attempt at the code. The issue is I'm getting a class cast exception because it does not like my cast to User from authentication.
Error
java.lang.ClassCastException: org.springframework.security.core.userdetails.User cannot be cast to com.example.model.User
Code
#Component
public class SecurityHandler implements AuthenticationSuccessHandler {
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Autowired
private UserService userService;
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication) throws IOException {
// Exception throw here during cast
User user = (User)authentication.getPrincipal();
user.setLastLogin(new Date());
userService.saveUser(user);
handle(request, response, authentication);
}
protected void handle(HttpServletRequest request,
HttpServletResponse response, Authentication authentication) throws IOException {
String targetUrl = "/dashboard";
redirectStrategy.sendRedirect(request, response, targetUrl);
}
}
I have a rest api where I am authenticating using spring security Basic Authorization where client sends username and password for each request.
Now, I wanted to implement token based authentication where I will send a token in response header when user is authenticated at first. For further requests, client can include that token in the header which will be used to authenticate the user to the resources. I have two authentication providers tokenAuthenticationProvider and daoAuthenticationProvider
#Component
public class TokenAuthenticationProvider implements AuthenticationProvider {
#Autowired
private TokenAuthentcationService service;
#Override
public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
final HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
final String token = request.getHeader(Constants.AUTH_HEADER_NAME);
final Token tokenObj = this.service.getToken(token);
final AuthenticationToken authToken = new AuthenticationToken(tokenObj);
return authToken;
}
#Override
public boolean supports(final Class<?> authentication) {
return AuthenticationToken.class.isAssignableFrom(authentication);
}
}
And in daoAuthenticationProvider I am setting custom userDetailsService and authenticating against user login details by fetching it from the database (which is working fine as long as user name and password are passed using Authorization:Basic bGllQXBpVXNlcjogN21wXidMQjRdTURtR04pag== as header)
But when I include token in the header using X-AUTH-TOKEN (which is Constants.AUTH_HEADER_NAME), tokenAuthenticationProvider is not being called. I am getting error as
{"timestamp":1487626368308,"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource","path":"/find"}
And here is how I am adding authentication providers.
#Override
public void configure(final AuthenticationManagerBuilder auth) throws Exception {
final UsernamePasswordAuthenticationProvider daoProvider = new
UsernamePasswordAuthenticationProvider(this.service, this.passwordEncoder());
auth.authenticationProvider(this.tokenAuthenticationProvider);
auth.authenticationProvider(daoProvider);
}
Please suggest how can I implement Token based authentication without hurting the current behavior of spring security.
Here is how I was able to implement token based authentication and basic authentication
SpringSecurityConfig.java
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Override
public void configure(final AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(this.participantService).passwordEncoder(this.passwordEncoder());
}
#Override
protected void configure(final HttpSecurity http) throws Exception
{
//Implementing Token based authentication in this filter
final TokenAuthenticationFilter tokenFilter = new TokenAuthenticationFilter();
http.addFilterBefore(tokenFilter, BasicAuthenticationFilter.class);
//Creating token when basic authentication is successful and the same token can be used to authenticate for further requests
final CustomBasicAuthenticationFilter customBasicAuthFilter = new CustomBasicAuthenticationFilter(this.authenticationManager() );
http.addFilter(customBasicAuthFilter);
}
}
TokenAuthenticationFilter.java
public class TokenAuthenticationFilter extends GenericFilterBean
{
#Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
throws IOException, ServletException
{
final HttpServletRequest httpRequest = (HttpServletRequest)request;
//extract token from header
final String accessToken = httpRequest.getHeader("header-name");
if (null != accessToken) {
//get and check whether token is valid ( from DB or file wherever you are storing the token)
//Populate SecurityContextHolder by fetching relevant information using token
final User user = new User(
"username",
"password",
true,
true,
true,
true,
authorities);
final UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
}
CustomBasicAuthenticationFilter.java
#Component
public class CustomBasicAuthenticationFilter extends BasicAuthenticationFilter {
#Autowired
public CustomBasicAuthenticationFilter(final AuthenticationManager authenticationManager) {
super(authenticationManager);
}
#Override
protected void onSuccessfulAuthentication(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response, final Authentication authResult) {
//Generate Token
//Save the token for the logged in user
//send token in the response
response.setHeader("header-name" , "token");
}
}
As our CustomBasicAuthenticationFilter has been configured and added as a filter to the spring security,
Whenever basic authentication is successful the request will be redirected to onSuccessfulAuthentication where we set the token and send it in the response with some header "header-name".
If "header-name" is sent for further request, then the request will go through TokenAuthenticationFilter first before attempting to try Basic Authentication.
You can try setting your custom AuthenticationToken token in your authentication filter, for example:
public class AuthenticationFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
final String authTokenHeader = ((HttpServletRequest)request).getHeader(Constants.AUTH_HEADER_NAME);
if (authTokenHeader != null) {
SecurityContextHolder.getContext().setAuthentication(createAuthenticationToken(authTokenHeader));
}
chain.doFilter( request, response );
}
}
I would like to track user's sessions. I am interested in getting the user logname, context he accessed and the time when he accessed a certain context.
I was thinking of using a class that implements HttpSessionListener (overriding sessionCreated(final HttpSessionEvent se) and sessionDestroyed(final HttpSessionEvent se)) but on these methods I don't get access to the request (from where I could pull the user's logname and context he accessed).
Any suggestions are welcome.
I think a servlet Filter is more suitable for what you want. I suggest to write a custom filter around all the urls you want to track.
In the doFilter() method you have access to the HttpServletRequest as needed. From the request object you can get HttpSession too.
Here is an example:
#WebFilter("/*")
public class TrackingFilter implements Filter {
private FilterConfig filterConfig;
#Override
public void init(FilterConfig config) throws ServletException {
this.filterConfig = config;
}
#Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpSession session = request.getSession(false);
String loggedInUser = "Unregistered user";
//assuming you have a session attribute named user with the username
if(session != null && session.getAttribute("user") != null) {
loggedInUser = (String) session.getAttribute("user");
}
Date accessedDate = new Date();
filterConfig.getServletContext().log(
String.format("%s accessed context %s on %tF %tT",
loggedInUser, request.getRequestURI() ,
accessedDate, accessedDate)
);
chain.doFilter(req, res);
}
#Override
public void destroy() {
}
}
See also: JavaEE6 tutorial section about filters.