I have been struggling to get waffle to work with spring 4.2.5 using spring java configuration. And I thought I might as well help others in the same situation.
We use a custom preWaffle and postWaffle filter to authenticate that the user exists in our database after it has been validated via waffles NTLM protocol.
We also have methods for authorization of a user actions using the EnableGlobalMethodSecurity annotation.
To get this working in spring java configuration was trouble some to say the least. You can find our solution in the answer below. I hope it will help.
SpringConfiguration.java
// ... imports
#Configuration
#EnableWebMvc
#EnableScheduling
#PropertySources({
#PropertySource("classpath:app.properties")
// ... Properties sources
})
#EnableTransactionManagement
#ComponentScan(basePackages = "com.our.package")
public class SpringConfiguration extends WebMvcConfigurerAdapter {
// Our Spring configuration ...
}
SecurityConfiguration.java
// ... imports
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, proxyTargetClass = true)
#Order(1)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
// Authentication manager configuration
#Autowired
private WindowsAuthenticationProviderWrapper authProvider;
#Autowired
private AuthenticationManagerBuilder auth;
#Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider);
}
#Bean
public AuthenticationManager authenticationManager() throws Exception {
return auth.getObject();
}
// Waffle configuration
#Bean
public Filter customPreAuthSecurityFilter() {
return new CustomPreAuthSecurityFilter();
}
#Bean
public Filter customNegotiateSecurityFilter() {
return new CustomNegotiateSecurityFilter();
}
#Bean
public WindowsAuthProviderImpl waffleAuthProvider(){
return new WindowsAuthProviderImpl();
}
#Bean(name="negotiateSecurityFilterProvider")
#Autowired
public NegotiateSecurityFilterProvider negotiateSecurityFilterProvider(){
NegotiateSecurityFilterProvider bean = new NegotiateSecurityFilterProvider(waffleAuthProvider());
List<String> protocols = new ArrayList<>();
protocols.add("Negotiate");
bean.setProtocols(protocols);
return bean;
}
#Bean
public BasicSecurityFilterProvider basicSecurityFilterProvider(){
return new BasicSecurityFilterProvider(waffleAuthProvider());
}
#Bean(name="waffleSecurityFilterProviderCollection")
#Autowired
public waffle.servlet.spi.SecurityFilterProviderCollection negotiateSecurityFilterProviderCollection() {
final List<SecurityFilterProvider> lsp = new ArrayList<>();
lsp.add(negotiateSecurityFilterProvider());
lsp.add(basicSecurityFilterProvider());
return new waffle.servlet.spi.SecurityFilterProviderCollection(lsp.toArray(new SecurityFilterProvider[]{}));
}
#Bean(name="negotiateSecurityFilterEntryPoint")
#Autowired
public waffle.spring.NegotiateSecurityFilterEntryPoint negotiateSecurityFilterEntryPoint() {
final waffle.spring.NegotiateSecurityFilterEntryPoint ep = new waffle.spring.NegotiateSecurityFilterEntryPoint();
ep.setProvider(negotiateSecurityFilterProviderCollection());
return ep;
}
#Bean(name="negotiateSecurityFilter")
#Autowired
public waffle.spring.NegotiateSecurityFilter waffleNegotiateSecurityFilter(){
waffle.spring.NegotiateSecurityFilter bean = new waffle.spring.NegotiateSecurityFilter();
bean.setRoleFormat("both");
bean.setPrincipalFormat("fqn");
bean.setAllowGuestLogin(false);
bean.setProvider(negotiateSecurityFilterProviderCollection());
return bean;
}
// Static Mappings
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/assets/**");
}
// Security filter chain
// The custom filters can be removed if you only use waffle
// but this is how we added them
#Override
protected void configure(HttpSecurity http) throws Exception {
// A user needs to have the role user and has to be authenticated
http.exceptionHandling()
.authenticationEntryPoint(negotiateSecurityFilterEntryPoint()).and()
.addFilterBefore(customPreAuthSecurityFilter(), BasicAuthenticationFilter.class)
.addFilterAfter(waffleNegotiateSecurityFilter(), BasicAuthenticationFilter.class)
.addFilterAfter(customNegotiateSecurityFilter(), BasicAuthenticationFilter.class)
.authorizeRequests().anyRequest().fullyAuthenticated();
}
}
To be able to autowire the waffle authProvider I created the following wrapperclass.
WindowsAuthenticationProviderWrapper.java
// ... imports
// This class purpose is only to make the Windows authentication provider autowireable in spring.
#Component
public class WindowsAuthenticationProviderWrapper extends WindowsAuthenticationProvider{}
As requested (Some code has been stripped due to security risks).
CustomPreAuthFilter.java
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* This filter removes the excess negoatiate header sent by IE. If the client
* has already authenticated, strip the Authorization header from the request.
*/
public class CustomPreAuthSecurityFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
SecurityContext sec = SecurityContextHolder.getContext();
HttpServletRequest req = (HttpServletRequest) servletRequest;
if(sec != null && sec.getAuthentication() != null) {
req = new CustomServletRequestWrapper(req);
}
try {
filterChain.doFilter(req, servletResponse);
} catch (RuntimeException e) {
sendUnauthorized((HttpServletResponse) servletResponse);
}
}
private void sendUnauthorized(HttpServletResponse response) throws IOException {
logger.warn("error logging in user");
SecurityContextHolder.clearContext();
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}
CustomNegotiateSecurityFilter.java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;
import waffle.servlet.WindowsPrincipal;
import waffle.spring.WindowsAuthenticationToken;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;
/**
* Handle post NTLM authentication against system database
*/
public class CustomNegotiateSecurityFilter extends GenericFilterBean {
#Autowired
private UserDAO userDAO;
#Autowired
Environment env;
private static final Logger LOGGER = LoggerFactory.getLogger(CustomNegotiateSecurityFilter.class);
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) req;
final HttpServletResponse response = (HttpServletResponse) res;
SecurityContext sec = SecurityContextHolder.getContext();
Authentication authentication = sec.getAuthentication();
// Continue filter chain if we are anonymously authenticated or if DB authentication has already happened.
if (authentication != null && authentication.getClass() == WindowsAuthenticationToken.class) {
// The user is Authenticated with NTLM but needs to be checked against the DB.
User user;
try {
// fetch user from DB ...
} catch (Exception e) {
// The could not be found in the DB.
sendUnauthorized(response);
return;
}
// The user was found in the DB.
WindowsPrincipal principal = (WindowsPrincipal)authentication.getPrincipal();
final CustomAuthenticationToken token = new CustomAuthenticationToken(principal); // This class extends WindowsAuthenticationToken
// add roles to token ...
sec.setAuthentication(token);
}
chain.doFilter(request, response);
}
private void sendUnauthorized(HttpServletResponse response) throws IOException {
logger.warn("Could not log in user");
SecurityContextHolder.clearContext();
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
private void addRoleToAuthentication(WindowsAuthenticationToken authentication, String role) {
for(GrantedAuthority authority : authentication.getAuthorities()) {
if(authority.getAuthority().equals(role)) {
return;
}
}
authentication.getAuthorities().add(new SimpleGrantedAuthority(role));
}
}
EDIT
For those who asked about here is one implementation. CustomServletRequestWrapper:
class CustomServletRequestWrapper extends HttpServletRequestWrapper {
public CustomServletRequestWrapper(HttpServletRequest request) {
super(request);
}
public String getHeader(String name) {
if(name.equals("Authorization"))
return null;
String header = super.getHeader(name);
return (header != null) ? header : super.getParameter(name); // Note: you can't use getParameterValues() here.
}
public Enumeration getHeaderNames() {
List<String> names = Collections.list(super.getHeaderNames());
names.addAll(Collections.list(super.getParameterNames()));
names.remove("Authorization");
return Collections.enumeration(names);
}
}
If you need more information don't hessitate to ask.
Related
I use Spring MVC (4.0.1) as a backend for rest services and angularjs as frontend.
every request to my server backend has a http-header with a session id
I can read this header in my server backend with the following code:
#Autowired
protected HttpServletRequest request;
String xHeader=request.getHeader("X-Auth-Token"); //returns the sessionID from the header
Now I call this method getPermission(xHeader) it return only true or false. If the user exists in my DB it return true else false!
I want now create a filter with this behavior, that checks every request if the user have the permission to access my controllers! But if the method returns false it should send back a 401 error and not reach my controller!
How can I do this and create my own filter? I use only Java Config and no XML.
I think I must add the filter here:
public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Filter[] getServletFilters() {
MyOwnFilter=new MyOwnFilter();
return new Filter[] {MyOwnFilter};
}
}
Alternative to Filters, you can use HandlerInterceptor.
public class SessionManager implements HandlerInterceptor{
// This method is called before the controller
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
String xHeader = request.getHeader("X-Auth-Token");
boolean permission = getPermission(xHeader);
if(permission) {
return true;
}
else {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
return false;
// Above code will send a 401 with no response body.
// If you need a 401 view, do a redirect instead of
// returning false.
// response.sendRedirect("/401"); // assuming you have a handler mapping for 401
}
return false;
}
#Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
}
#Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}
}
And then add this interceptor to your webmvc config.
#EnableWebMvc
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Bean
SessionManager getSessionManager() {
return new SessionManager();
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(getSessionManager())
.addPathPatterns("/**")
.excludePathPatterns("/resources/**", "/login");
// assuming you put your serve your static files with /resources/ mapping
// and the pre login page is served with /login mapping
}
}
Below is the filter to perform the logic you have mentioned
#WebFilter("/*")
public class AuthTokenFilter implements Filter {
#Override
public void destroy() {
// ...
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
//
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String xHeader = ((HttpServletRequest)request).getHeader("X-Auth-Token");
if(getPermission(xHeader)) {
chain.doFilter(request, response);
} else {
request.getRequestDispatcher("401.html").forward(request, response);
}
}
}
And you got it right, the spring config should be following.
public class MyWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Filter[] getServletFilters() {
return new Filter[]{new AuthTokenFilter()};
}
}
Spring can use filters, but they recommend that you use their version of filters, known as an interceptor
http://viralpatel.net/blogs/spring-mvc-interceptor-example/
There is a quick run through of how they work. They are nearly identical to filters, but designed to work inside the Spring MVC lifecycle.
I assume that you are trying to implement some kind of OAuth security which is based on jwt token.
Nowdays there are several ways to do so but here is my favourite one:
Here is how the filter looks like:
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.filter.GenericFilterBean;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureException;
public class JwtFilter extends GenericFilterBean {
#Override
public void doFilter(final ServletRequest req,
final ServletResponse res,
final FilterChain chain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) req;
final String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new ServletException("Missing or invalid Authorization header.");
}
final String token = authHeader.substring(7); // The part after "Bearer "
try {
final Claims claims = Jwts.parser().setSigningKey("secretkey")
.parseClaimsJws(token).getBody();
request.setAttribute("claims", claims);
}
catch (final SignatureException e) {
throw new ServletException("Invalid token.");
}
chain.doFilter(req, res);
}
}
Pretty simple there is the user controller also where you can find the login method:
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletException;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
#RestController
#RequestMapping("/user")
public class UserController {
private final Map<String, List<String>> userDb = new HashMap<>();
public UserController() {
userDb.put("tom", Arrays.asList("user"));
userDb.put("sally", Arrays.asList("user", "admin"));
}
#RequestMapping(value = "login", method = RequestMethod.POST)
public LoginResponse login(#RequestBody final UserLogin login)
throws ServletException {
if (login.name == null || !userDb.containsKey(login.name)) {
throw new ServletException("Invalid login");
}
return new LoginResponse(Jwts.builder().setSubject(login.name)
.claim("roles", userDb.get(login.name)).setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS256, "secretkey").compact());
}
#SuppressWarnings("unused")
private static class UserLogin {
public String name;
public String password;
}
#SuppressWarnings("unused")
private static class LoginResponse {
public String token;
public LoginResponse(final String token) {
this.token = token;
}
}
}
Of course we have Main where you can see the filter bean:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.embedded.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
#EnableAutoConfiguration
#ComponentScan
#Configuration
public class WebApplication {
#Bean
public FilterRegistrationBean jwtFilter() {
final FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new JwtFilter());
registrationBean.addUrlPatterns("/api/*");
return registrationBean;
}
public static void main(final String[] args) throws Exception {
SpringApplication.run(WebApplication.class, args);
}
}
Last but not least there is an example controller:
import io.jsonwebtoken.Claims;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/api")
public class ApiController {
#SuppressWarnings("unchecked")
#RequestMapping(value = "role/{role}", method = RequestMethod.GET)
public Boolean login(#PathVariable final String role,
final HttpServletRequest request) throws ServletException {
final Claims claims = (Claims) request.getAttribute("claims");
return ((List<String>) claims.get("roles")).contains(role);
}
}
Here is a link to GitHub all thanks goes to nielsutrecht for the great work I have used this project as base and it works perfectly.
You can also implement it using an aspect with a pointcut that targets a certain annotation. I have written a library that enables you to use annotations that perform authorization checks based on a JWT token.
You can find the project with all the documentation on: https://github.com/nille85/jwt-aspect. I have used this approach multiple times in order to secure a REST Backend that is consumed by a single page application.
I have also documented on my blog how you can use it in a Spring MVC Application: http://www.nille.be/security/creating-authorization-server-using-jwts/
The following is an extract from the example project on https://github.com/nille85/auth-server
The example underneath contains a protected method getClient. The annotation #Authorize that the aspect uses checks if the value from the "aud jwt claim" matches the clientId parameter that is annotated with #ClaimValue. If it matches, the method can be entered. Otherwise an exception is thrown.
#RestController
#RequestMapping(path = "/clients")
public class ClientController {
private final ClientService clientService;
#Autowired
public ClientController(final ClientService clientService) {
this.clientService = clientService;
}
#Authorize("hasClaim('aud','#clientid')")
#RequestMapping(value = "/{clientid}", method = RequestMethod.GET, produces = "application/json")
#ResponseStatus(value = HttpStatus.OK)
public #ResponseBody Client getClient(#PathVariable(value = "clientid") #ClaimValue(value = "clientid") final String clientId) {
return clientService.getClient(clientId);
}
#RequestMapping(value = "", method = RequestMethod.GET, produces = "application/json")
#ResponseStatus(value = HttpStatus.OK)
public #ResponseBody List<Client> getClients() {
return clientService.getClients();
}
#RequestMapping(path = "", method = RequestMethod.POST, produces = "application/json")
#ResponseStatus(value = HttpStatus.OK)
public #ResponseBody Client registerClient(#RequestBody RegisterClientCommand command) {
return clientService.register(command);
}
}
The Aspect itself can be configured like:
#Bean
public JWTAspect jwtAspect() {
JWTAspect aspect = new JWTAspect(payloadService());
return aspect;
}
The PayloadService that is needed can for example be implemented like:
public class PayloadRequestService implements PayloadService {
private final JWTVerifier verifier;
public PayloadRequestService(final JWTVerifier verifier){
this.verifier = verifier;
}
#Override
public Payload verify() {
ServletRequestAttributes t = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = t.getRequest();
final String jwtValue = request.getHeader("X-AUTH");
JWT jwt = new JWT(jwtValue);
Payload payload =verifier.verify(jwt);
return payload;
}
}
You can create and configure your own filter by doing following steps.
1) Create your class by implementing the filter interface and override its methods.
public class MyFilter implements javax.servlet.Filter{
public void destroy(){}
public void doFilter(Request, Response, FilterChain){//do what you want to filter
}
........
}
2) Now configure your filter in web.xml
<filter>
<filter-name>myFilter</filter-name>
<filter-class>MyFilter</filter-class>
</filter>
3) Now provide url mapping of the filter.
<filter-mapping>
<filter-name>myFilter</filter-name>
<url-pattern>*</url-pattern>
</filter-mapping>
4) Now restart your server and check all the web request will first come to MyFilter and then proceed to the respective controller.
Hopefully it will be the required answer.
Your approach looks correct.
Once I have used something similar to following (Removed most of the lines and kept it simple).
public class MvcDispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
EnumSet<DispatcherType> dispatcherTypes = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.ERROR);
FilterRegistration.Dynamic monitoringFilter = servletContext.addFilter("monitoringFilter", MonitoringFilter.class);
monitoringFilter.addMappingForUrlPatterns(dispatcherTypes, false, "/api/admin/*");
}
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { WebMvcConfig.class };
}
#Override
protected Class<?>[] getServletConfigClasses() {
return null;
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
Also you need a custom filter looks like below.
public class CustomXHeaderFilter implements Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String xHeader = request.getHeader("X-Auth-Token");
if(YOUR xHeader validation fails){
//Redirect to a view
//OR something similar
return;
}else{
//If the xHeader is OK, go through the chain as a proper request
chain.doFilter(request, response);
}
}
#Override
public void destroy() {
}
#Override
public void init(FilterConfig arg0) throws ServletException {
}
}
Hope this helps.
Additionally you can use FilterRegistrationBean if you Spring Boot. It does the same thing (I think so) which FilterRegistration.Dynamic does.
How to handle UsernameNotFoundException ?
In spring security when username not found the UserDetailsService implementation throws a UsernameNotFoundException. For example like this:
#Override
#Transactional
public UserDetails loadUserByUsername(java.lang.String username) throws UsernameNotFoundException {
logger.info("Load user by username: {}", username);
User user = userRepository.findUserByUsername(username).orElseThrow(
() -> new UsernameNotFoundException("User Not Found with -> username or email: " + username));
return UserPrinciple.build(user);
}
I would like to build a custom "User not found REST response".
How should I catch/handle this exception? I have implemented a handler method in the WebSecurityConfigurerAdapter implementation the handler:
private static void handleException(HttpServletRequest req, HttpServletResponse rsp, AuthenticationException e)
throws IOException {
PrintWriter writer = rsp.getWriter();
writer.println(new ObjectMapper().writeValueAsString(new AuthResponse("", null, null, null, null,
"Authentication failed.", false)));
rsp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
But this method should wait for an AuthenticationException exception which, and during runtime the type of the exception is java.lang.NullPointerException so I'm not able to cast or retrieve the the initial UsernameNotFoundException.
Any advice would be appreciated.
Many many regards :).
Security layer comes before anything in the controllers and #ControllerAdvice.
Hence #ControllerAdvice isn't an option since UsernameNotFoundException which is a subclass of AuthenticationException is thrown during authenticaton, making your exception handlers in #ControllerAdvice unreachable.
You can only use #ControllerAdvice and ResponseEntityExceptionHandler if you are throwing UsernameNotFoundException inside controller or any others beans referenced from the controllers.
Here is my suggestion - that you implement AuthenticationFailureHandler and use it with AuthenticationFilter that you are using for your security configuration.
Spring boot security comes with about 4 handler interfaces for security related issues
AccessDeniedHandler - this handles issues like when a user not having required roles.
AuthenticationEntryPoint - this handles issues like when a user tries to access a resource without appropriate authentication elements.
AuthenticationFailureHandler - this handles issues like when a user is not found(i.e. UsernameNotFoundException) or other exceptions thrown inside authentication provider. In fact, this handles other authentication exceptions that are not handled by AccessDeniedException and AuthenticationEntryPoint.
AuthenticationSuccessHandler - this helps to do stuff like redirection after a user is successfully authenticated.
See the following example snippets for the implementation of all the 4 interfaces. Please customize these to your taste.
AccessDeniedHandler implementation
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
#Component
public class RestAccessDeniedHandler implements AccessDeniedHandler {
#Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
Map<String,Object> response = new HashMap<>();
response.put("status","34");
response.put("message","unauthorized api access");
//httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
OutputStream out = httpServletResponse.getOutputStream();
ObjectMapper mapper = new ObjectMapper();
mapper.writerWithDefaultPrettyPrinter().writeValue(out,response);
//mapper.writeValue(out, response);
out.flush();
}
}
AuthenticationEntryPoint Implementation
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
#Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
Map<String,Object> response = new HashMap<>();
response.put("status","34");
response.put("message","unauthorized access");
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
OutputStream out = httpServletResponse.getOutputStream();
ObjectMapper mapper = new ObjectMapper();
mapper.writerWithDefaultPrettyPrinter().writeValue(out, response);
out.flush();
}
}
AuthenticationFailureHandler implementation
package com.ibiller.webservices.security;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
#Component
public class RestAuthenticationFailureHandler implements AuthenticationFailureHandler
{
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse httpServletResponse,
AuthenticationException ex) throws IOException, ServletException
{
Map<String,Object> response = new HashMap<>();
response.put("status","34");
response.put("message","unauthorized access");
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
OutputStream out = httpServletResponse.getOutputStream();
ObjectMapper mapper = new ObjectMapper();
mapper.writerWithDefaultPrettyPrinter().writeValue(out, response);
out.flush();
}
}
AuthenticationSuccessHandler implementation
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
#Component
public class RestSuccessHandler implements AuthenticationSuccessHandler {
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
Set<String> roles =
AuthorityUtils.authorityListToSet(authentication.getAuthorities());
if (roles.contains("ROLE_ADMIN")) {
//do something
}
}
}
This is the Security configuration that extends WebSecurityConfigurerAdapter that connects everything together.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private static final RequestMatcher PROTECTED_URLS = new OrRequestMatcher(
new AntPathRequestMatcher("/v1/**"),new AntPathRequestMatcher("/admin/**")
);
AuthenticationProvider provider;
public SecurityConfiguration(final AuthenticationProvider authenticationProvider) {
super();
this.provider=authenticationProvider;
}
#Override
protected void configure(final AuthenticationManagerBuilder auth) {
auth.authenticationProvider(provider);
}
#Override
public void configure(final WebSecurity webSecurity) {
webSecurity.ignoring().antMatchers("/info/**");//url that will be ignored
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler())
.authenticationEntryPoint(authenticationEntryPoint())
.and()
.authenticationProvider(provider)
.addFilterBefore(authenticationFilter(), AnonymousAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/v1/**").hasRole("API")
.antMatchers("/admin/**").hasAnyRole("SUPER_ADMIN","ADMIN")
.and()
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.logout().disable();
}
#Bean
AuthenticationFilter authenticationFilter() throws Exception {
final AuthenticationFilter filter = new AuthenticationFilter(PROTECTED_URLS);
filter.setAuthenticationManager(authenticationManager());
filter.setAuthenticationSuccessHandler(successHandler());
filter.setAuthenticationFailureHandler(authenticationFailureHandler());
return filter;
}
#Bean
RestAccessDeniedHandler accessDeniedHandler() {
return new RestAccessDeniedHandler();
}
#Bean
RestAuthenticationEntryPoint authenticationEntryPoint() {
return new RestAuthenticationEntryPoint();
}
#Bean
RestAuthenticationFailureHandler authenticationFailureHandler(){
return new RestAuthenticationFailureHandler();
}
#Bean
RestSuccessHandler successHandler(){
return new RestSuccessHandler();
}
}
I don't know the structure of your project, but a usual solution in this case is using #ControllerAdvice mechanism (separated class or in controller):
#ControllerAdvice
public class CustomExceptionHandler {
#ExceptionHandler(value = UsernameNotFoundException.class)
public ResponseEntity handle(final UsernameNotFoundException exception) {
...//set headers, response attributes and response body
}
}
In the class that inherits from UsernamePasswordAuthenticationFilter you have to override the method unsuccessfulAuthentication
It calls the super class method, but what it does it's it redirects to another context of error, which makes the Authorization filter activates.
Instead, just fill the request information as your client is expecting (Json in my case)
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse res, AuthenticationException failed) throws IOException, ServletException {
res.addHeader("Access-Control-Allow-Origin", "*");
res.setStatus(HttpServletResponse.SC_OK);
ObjectMapper mapper = new ObjectMapper();
ObjectNode message = mapper.createObjectNode();
message.put("success", false);
message.put("message", "Invalid credentials");
String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(message);
PrintWriter out = res.getWriter();
res.setContentType("application/json");
res.setCharacterEncoding("UTF-8");
out.print(json);
out.flush();
}
I have a spring boot app with the following web security configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/login**", "/signup**").permitAll()
.and()
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtAuthenticationFilter,
UsernamePasswordAuthenticationFilter.class);
}
The JWTAuthenticationFilter looks like this :
#Component
public class JWTAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
#Autowired
private UserDetailsService customUserDetailsService;
private static Logger logger = LoggerFactory.getLogger(JWTAuthenticationFilter.class);
private final static UrlPathHelper urlPathHelper = new UrlPathHelper();
public JWTAuthenticationFilter() {
super("/greeting");
setAuthenticationManager(new NoOpAuthenticationManager());
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
Authentication authentication = AuthenticationService.getAuthentication(request, customUserDetailsService);
return getAuthenticationManager().authenticate(authentication);
}
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
logger.debug("failed authentication while attempting to access " + urlPathHelper.getPathWithinApplication((HttpServletRequest) request));
}
}
1. Authentication is done successfuly, I even see the following log line in the console:
2017-05-19 03:11:42 [https-jsse-nio-8443-exec-4] DEBUG c.b.c.s.a.j.JWTAuthenticationFilter -
Authentication success. Updating SecurityContextHolder to contain: org.springframework.security.authentication.UsernamePasswordAuthenticationToken#f297a5c8: Principal: administrator; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: USER_ROLE
but still the client side, 403 response is recieved.
2. I want to make this authentication filter execute for all endpoints, except those with permitAll in the web security configurer. How should I do that?
Reason for that is it's redirecting to the default redirect URL in AbstractAuthenticationProcessingFilter which is /. To override this behavior you'll need to override successfulAuthentication().
unsuccessfulAuthentication() method needs to send an authentication error.
Here's the implementation of both of these methods.
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authResult);
chain.doFilter(request, response);
}
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException failed) throws IOException, ServletException {
logger.debug("failed authentication while attempting to access "
+ urlPathHelper.getPathWithinApplication((HttpServletRequest) request));
//Add more descriptive message
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
"Authentication Failed");
}
Following is a self contained working example. I have populated dummy authentication object for all requests, you'll need to use your own user details service to validate and then populate authentication object conditionally.
package com.test;
import java.io.IOException;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UrlPathHelper;
#SpringBootApplication
public class TestSpringSecurityCustomApplication {
public static void main(String[] args) {
SpringApplication.run(TestSpringSecurityCustomApplication.class, args);
}
}
#Configuration
class CustomWebSecurity extends WebSecurityConfigurerAdapter {
#Autowired
private JWTAuthenticationFilter jwtAuthenticationFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("Configuring security");
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.anyRequest().authenticated()
.and().csrf().disable();
}
#Override
public void configure(WebSecurity web)
throws Exception {
web.ignoring().antMatchers("/login/**", "/signup/**");
}
/* Stopping spring from adding filter by default */
#Bean
public FilterRegistrationBean rolesAuthenticationFilterRegistrationDisable(JWTAuthenticationFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}
}
#RestController
#RequestMapping("greeting")
class TestService {
#RequestMapping("test")
public String test() {
return "Hello World " + new Date();
}
}
#Component
class JWTAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
#Autowired
private UserDetailsService customUserDetailsService;
private static Logger logger = LoggerFactory.getLogger(JWTAuthenticationFilter.class);
private final static UrlPathHelper urlPathHelper = new UrlPathHelper();
public JWTAuthenticationFilter() {
super("/**");
setAuthenticationManager(new NoOpAuthenticationManager());
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
Authentication authentication = AuthenticationService.getAuthentication(request, customUserDetailsService);
return getAuthenticationManager().authenticate(authentication);
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authResult);
chain.doFilter(request, response);
}
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException failed) throws IOException, ServletException {
logger.debug("failed authentication while attempting to access "
+ urlPathHelper.getPathWithinApplication((HttpServletRequest) request));
//Add more descriptive message
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
"Authentication Failed");
}
}
class AuthenticationService {
public static Authentication getAuthentication(HttpServletRequest request, UserDetailsService userDetailsService) {
String username = "TEST_USER";// get this from the token or request
UserDetails user = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(user,
user.getPassword(), user.getAuthorities());
//Use following to indicate that authentication failed, if user not found or role doesn't match
boolean hasAuthenticationFailed = false;
if(hasAuthenticationFailed) {
throw new AuthenticationException(username){};
}
return authentication;
}
}
#Component
class CustomUserDetailsService implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// Returning dummy user, use your own logic for example load from
// database
List<SimpleGrantedAuthority> authorities = Arrays.asList(new SimpleGrantedAuthority(("ROLE_USER")));
User user = new User("TEST_USER", "NO_PASSWORD", authorities);
System.out.println("user : " + user.getUsername());
return user;
}
}
class NoOpAuthenticationManager implements AuthenticationManager {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
return authentication;
}
}
Edit
With custom security filters, permitAll() method doesn't seem to have any effect. So following method should be overridden in WebSecurityConfigurerAdapter to ignore URLs
#Override
public void configure(WebSecurity web)
throws Exception {
web.ignoring().antMatchers("/login/**", "/signup/**");
}
Note: I have modified above code to use the same. Also if you want to ignore sub URLs to login ie. login/dafdsf, then you should use /login/** instead of /login**
I have a spring-boot backend application that authorizes users using our JASIG-CAS server and redirects them to frontend which can now access protected resources from backend. Now I need to add a mobile client. Up until now my configuration had SimpleUrlAuthenticationSuccessHandler with hardcoded url of my frontend in CasAuthenticationFilter like so:
public CasAuthenticationFilter casAuthenticationFilter() throws Exception {
CasAuthenticationFilter authenticationFilter = new CasAuthenticationFilter(); authenticationFilter.setAuthenticationManager(authenticationManager());
authenticationFilter.setServiceProperties(serviceProperties());
authenticationFilter.setFilterProcessesUrl("/auth/cas");
SimpleUrlAuthenticationSuccessHandler simpleUrlAuthenticationSuccessHandler =
new SimpleUrlAuthenticationSuccessHandler(env.getRequiredProperty(CAS_REDIRECT_TARGET));
authenticationFilter.setAuthenticationSuccessHandler(simpleUrlAuthenticationSuccessHandler);
return authenticationFilter;
}//CasAuthenticationFilter
But now my mobile client should open browser, show familiar CAS login page, authenticate user, redirect to backend which will issue a deep-link to mobile application. The problem is the hardcoded redirection target which points to frontend. The request from CAS looks the same regardles if it was triggerd from frontend or mobile because both use browsers, so I can't distinguish them using my own AuthenticationSuccessHandler. In a desperate act I tried constructing two different authentication flows using using the same CAS server but different callback endpoints. Here is this monster:
package com.my.company.config;
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.jasig.cas.client.validation.Cas20ServiceTicketValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken;
import org.springframework.security.cas.authentication.CasAuthenticationProvider;
import org.springframework.security.cas.authentication.NullStatelessTicketCache;
import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
import org.springframework.security.cas.web.CasAuthenticationFilter;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.inject.Inject;
import javax.servlet.*;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final Logger log = LoggerFactory.getLogger(SecurityConfiguration.class);
private static final String CAS_URL_SERVER = "cas.url.server";
private static final String CAS_URL_LOGIN = "cas.url.login";
private static final String CAS_URL_LOGOUT = "cas.url.logout";
private static final String CAS_URL_SERVICE = "cas.url.service";
private static final String CAS_URL_CALLBACK = "cas.url.callback";
private static final String CAS_REDIRECT_TARGET = "cas.redirect.target";
#Inject
private Environment env;
#Inject
private AjaxAuthenticationSuccessHandler ajaxAuthenticationSuccessHandler;
#Inject
private AjaxAuthenticationFailureHandler ajaxAuthenticationFailureHandler;
#Inject
#Qualifier("casUserDetailsService")
private AuthenticationUserDetailsService<CasAssertionAuthenticationToken> casAuthenticationUserDetailsService;
#Inject
#Qualifier("formUserDetailsService")
private UserDetailsService userDetailsService;
#Inject
private Http401UnauthorizedEntryPoint authenticationEntryPoint;
#Bean
public Cas20ServiceTicketValidator cas20ServiceTicketValidator() {
return new Cas20ServiceTicketValidator(env.getRequiredProperty(CAS_URL_SERVER));
}
#Bean(name="webAuthProvider")
public CasAuthenticationProvider webCasAuthenticationProvider() {
CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider();
casAuthenticationProvider.setTicketValidator(cas20ServiceTicketValidator());
casAuthenticationProvider.setStatelessTicketCache(new NullStatelessTicketCache());
casAuthenticationProvider.setKey("CAS_WEB_AUTHENTICATION_PROVIDER");
casAuthenticationProvider.setAuthenticationUserDetailsService(casAuthenticationUserDetailsService);
casAuthenticationProvider.setMessageSource(new SpringSecurityMessageSource());
casAuthenticationProvider.setServiceProperties(webServiceProperties());
return casAuthenticationProvider;
}//CasAuthenticationProvider
#Bean(name="mobileAuthProvider")
public CasAuthenticationProvider mobileCasAuthenticationProvider() {
CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider();
casAuthenticationProvider.setTicketValidator(cas20ServiceTicketValidator());
casAuthenticationProvider.setStatelessTicketCache(new NullStatelessTicketCache());
casAuthenticationProvider.setKey("CAS_MOBILE_AUTHENTICATION_PROVIDER");
casAuthenticationProvider.setAuthenticationUserDetailsService(casAuthenticationUserDetailsService);
casAuthenticationProvider.setMessageSource(new SpringSecurityMessageSource());
casAuthenticationProvider.setServiceProperties(mobileServiceProperties());
return casAuthenticationProvider;
}//CasAuthenticationProvider
#Bean
public SingleSignOutFilter singleSignOutFilter() {
SingleSignOutFilter filter = new SingleSignOutFilter();
return filter;
}//SingleSignOutFilter
#Inject
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder())
.and()
.authenticationProvider(mobileCasAuthenticationProvider())
.authenticationProvider(webCasAuthenticationProvider());
}
#Bean(name = "webCasFilter")
public CasAuthenticationFilter webCasAuthenticationFilter() throws Exception {
CasAuthenticationFilter authenticationFilter = new CasAuthenticationFilter();
authenticationFilter.setBeanName("webCasFilter");
authenticationFilter.setAuthenticationManager(authenticationManager());
authenticationFilter.setServiceProperties(webServiceProperties());
authenticationFilter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/auth/cas"));
//authenticationFilter.setFilterProcessesUrl("/auth/cas");
SimpleUrlAuthenticationSuccessHandler simpleUrlAuthenticationSuccessHandler =
new SimpleUrlAuthenticationSuccessHandler(env.getRequiredProperty(CAS_REDIRECT_TARGET));
authenticationFilter.setAuthenticationSuccessHandler(simpleUrlAuthenticationSuccessHandler);
return authenticationFilter;
}//CasAuthenticationFilter
#Bean(name = "mobileCasFilter")
public CasAuthenticationFilter mobileCasAuthenticationFilter() throws Exception {
CasAuthenticationFilter authenticationFilter = new CasAuthenticationFilter();
authenticationFilter.setBeanName("mobileCasFilter");
authenticationFilter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/auth/cas/mobile"));
authenticationFilter.setAuthenticationManager(authenticationManager());
authenticationFilter.setServiceProperties(mobileServiceProperties());
//authenticationFilter.setFilterProcessesUrl("/auth/cas/mobile");
SimpleUrlAuthenticationSuccessHandler simpleUrlAuthenticationSuccessHandler =
new SimpleUrlAuthenticationSuccessHandler("/mobile/deep-link");
authenticationFilter.setAuthenticationSuccessHandler(simpleUrlAuthenticationSuccessHandler);
return authenticationFilter;
}//CasAuthenticationFilter
#Bean(name="webCasAuthenticationEntryPoint")
public CasAuthenticationEntryPoint webCasAuthenticationEntryPoint() {
CasAuthenticationEntryPoint entryPoint = new CasAuthenticationEntryPoint();
entryPoint.setLoginUrl(env.getRequiredProperty(CAS_URL_LOGIN));
entryPoint.setServiceProperties(webServiceProperties());
return entryPoint;
}//CasAuthenticationEntryPoint
#Bean(name="mobileCasAuthenticationEntryPoint")
public CasAuthenticationEntryPoint mobileCasAuthenticationEntryPoint() {
CasAuthenticationEntryPoint entryPoint = new CasAuthenticationEntryPoint();
entryPoint.setLoginUrl(env.getRequiredProperty(CAS_URL_LOGIN));
entryPoint.setServiceProperties(mobileServiceProperties());
return entryPoint;
}//CasAuthenticationEntryPoint
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/scripts/**/*.{js,html}")
.antMatchers("/bower_components/**")
.antMatchers("/i18n/**")
.antMatchers("/assets/**")
.antMatchers("/swagger-ui/**")
.antMatchers("/test/**")
.antMatchers("/console/**");
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
private class CasRedirectionFilter implements Filter {
public void init(FilterConfig fConfig) throws ServletException {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
HttpServletResponse res = (HttpServletResponse) response;
//CasAuthenticationEntryPoint caep = casAuthenticationEntryPoint();
res.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
HttpServletRequest req = (HttpServletRequest) request;;
String contextPath = req.getRequestURI();
if(contextPath.equals("/api/login/mobile")){
String redirectUrl = "https://cas.server.com/cas/login?service=http://localhost:8080/auth/cas/mobile";
res.setHeader("Location", redirectUrl);
}else {
String redirectUrl = "https://cas.server.com/cas/login?service=http://localhost:8080/auth/cas";
res.setHeader("Location", redirectUrl);
}
}
public void destroy() {
}
}
#Bean
public FilterChainProxy loginFilter() throws Exception {
List<SecurityFilterChain> chains = new ArrayList<SecurityFilterChain>();
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/api/login/cas"), new CasRedirectionFilter()));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/api/login/mobile"), new CasRedirectionFilter()));
log.debug("loginFilter {}", chains);
return new FilterChainProxy(chains);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
.and()
.csrf()
.disable()
.addFilterBefore(mobileCasAuthenticationFilter(),CasAuthenticationFilter.class)
.addFilterBefore(webCasAuthenticationFilter(),CasAuthenticationFilter.class)
.addFilterBefore(singleSignOutFilter(), CasAuthenticationFilter.class)
.addFilter(loginFilter())
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.logout()
.logoutUrl("/api/logout")
.clearAuthentication(true)
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.logoutSuccessUrl(env.getRequiredProperty(CAS_URL_LOGOUT))
.permitAll()
.and()
.headers()
.frameOptions()
.disable()
.and()
.formLogin()
//.defaultSuccessUrl(env.getRequiredProperty(CAS_REDIRECT_TARGET), true)
.successHandler(ajaxAuthenticationSuccessHandler)
.failureHandler(ajaxAuthenticationFailureHandler)
.loginProcessingUrl("/api/authentication")
.usernameParameter("j_username")
.passwordParameter("j_password")
.permitAll()
.and()
.authorizeRequests()
.antMatchers(org.springframework.http.HttpMethod.OPTIONS, "/api/**").permitAll()
.antMatchers(org.springframework.http.HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/app/**").authenticated()
.antMatchers(HttpMethod.GET, "/api/login").authenticated()
.antMatchers("/api/login/mobile").authenticated()
.antMatchers("/api/login/cas").authenticated()
.antMatchers("/api/register").permitAll()
.antMatchers("/api/activate").permitAll()
.antMatchers("/api/authenticate").authenticated()
.antMatchers("/api/logs/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/api/**").authenticated()
.antMatchers("/metrics/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/mobile/**").permitAll()
.antMatchers("/health/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/trace/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/dump/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/shutdown/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/beans/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/configprops/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/info/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/autoconfig/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/env/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/trace/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/api-docs/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/protected/**").authenticated();
}
#EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
private static class GlobalSecurityConfiguration extends GlobalMethodSecurityConfiguration {
#Inject
ConferenceRepository conferenceRepository;
#Inject
UserRepository userRepository;
public GlobalSecurityConfiguration() {
super();
}
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
PermissionChecker permissionEvaluator = new PermissionChecker(conferenceRepository, userRepository);
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(permissionEvaluator);
return expressionHandler;
}
}
#Bean(name="webServiceProperties")
public ServiceProperties webServiceProperties() {
ServiceProperties serviceProperties = new ServiceProperties();
serviceProperties.setService("http://localhost:8080/auth/cas");
serviceProperties.setSendRenew(true);
serviceProperties.setAuthenticateAllArtifacts(true);
return serviceProperties;
}//serviceProperties
#Bean(name="mobileServiceProperties")
public ServiceProperties mobileServiceProperties() {
ServiceProperties serviceProperties = new ServiceProperties();
serviceProperties.setService("http://localhost:8080/auth/cas/mobile");
serviceProperties.setSendRenew(true);
serviceProperties.setAuthenticateAllArtifacts(true);
return serviceProperties;
}//serviceProperties
}
This works to some degree. When mobile authentication flow is issued it works as intended but when frontend issues /api/login/cas the TicketGrantingTicket from CAS is first checked using mobile filter against service=/auth/cas/mobile but was issued for service=/auth/cas which invalidates TGT and subsequent validation using casWebAuthenticationFilter uses that invalidated ticket which of course.
So now I'm out of ideas how to force CasAuthenticationFilter to process only certain tickets? Perhaps I'm so tangled up in my idea that I can't see simpler solution? Maybe I should do two separate http security configs?
EDIT:
It seems that it all boils down to the order in which I put AuthenticationProvider:
#Inject
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder())
.and()
.authenticationProvider(webCasAuthenticationProvider())
.authenticationProvider(mobileCasAuthenticationProvider());
}
When mobileAuthenticationProider() goes first then the mobile login works and web one doesn't when I switch the order in which they are called then mobile authentication fails and the web one starts to work.
Ok so I got it working, it doesn't look like the best, most robust solution so it probably nees some more investigation and care. Nevertheless here it goes:
#Bean
public AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> authenticationDetailsSource() {
return new AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails>() {
#Override
public WebAuthenticationDetails buildDetails(
HttpServletRequest request) {
return new CustomAuthenticationDetails(request);
}
};
}
#Bean
public AuthenticationProvider customAuthenticationProvider() {
return new AuthenticationProvider() {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String serviceUrl;
serviceUrl = ((CustomAuthenticationDetails) authentication.getDetails()).getURI();
if (serviceUrl.equals(env.getRequiredProperty(CAS_URL_CALLBACK_MOBILE))) {
return mobileCasAuthenticationProvider().authenticate(authentication);
} else {
return webCasAuthenticationProvider().authenticate(authentication);
}
}
public boolean supports(final Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class
.isAssignableFrom(authentication))
|| (CasAuthenticationToken.class.isAssignableFrom(authentication))
|| (CasAssertionAuthenticationToken.class
.isAssignableFrom(authentication));
}
};
}
I added my custom AuthenticationProvider which distinguishes between the two that I really needed. It does it using another custom class, namely CustomAuthenticationDetails which stores information about where the request came from.
public class CustomAuthenticationDetails extends WebAuthenticationDetails {
private final Logger log = LoggerFactory.getLogger(CustomAuthenticationDetails.class);
private final String URI;
private final String sessionId;
public CustomAuthenticationDetails(HttpServletRequest request) {
super(request);
this.URI = request.getRequestURI();
HttpSession session = request.getSession(false);
this.sessionId = (session != null) ? session.getId() : null;
}
public String getURI() {
return URI;
}
public String getSessionId() {
return sessionId;
}
}
And all this is wired together in AuthenticationFilter using authenticationFilter.setAuthenticationDetailsSource(authenticationDetailsSource());. Hope it can help someone in future problems or at least lead in the right direction.
I use Spring MVC (4.0.1) as a backend for rest services and angularjs as frontend.
every request to my server backend has a http-header with a session id
I can read this header in my server backend with the following code:
#Autowired
protected HttpServletRequest request;
String xHeader=request.getHeader("X-Auth-Token"); //returns the sessionID from the header
Now I call this method getPermission(xHeader) it return only true or false. If the user exists in my DB it return true else false!
I want now create a filter with this behavior, that checks every request if the user have the permission to access my controllers! But if the method returns false it should send back a 401 error and not reach my controller!
How can I do this and create my own filter? I use only Java Config and no XML.
I think I must add the filter here:
public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Filter[] getServletFilters() {
MyOwnFilter=new MyOwnFilter();
return new Filter[] {MyOwnFilter};
}
}
Alternative to Filters, you can use HandlerInterceptor.
public class SessionManager implements HandlerInterceptor{
// This method is called before the controller
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
String xHeader = request.getHeader("X-Auth-Token");
boolean permission = getPermission(xHeader);
if(permission) {
return true;
}
else {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
return false;
// Above code will send a 401 with no response body.
// If you need a 401 view, do a redirect instead of
// returning false.
// response.sendRedirect("/401"); // assuming you have a handler mapping for 401
}
return false;
}
#Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
}
#Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}
}
And then add this interceptor to your webmvc config.
#EnableWebMvc
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Bean
SessionManager getSessionManager() {
return new SessionManager();
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(getSessionManager())
.addPathPatterns("/**")
.excludePathPatterns("/resources/**", "/login");
// assuming you put your serve your static files with /resources/ mapping
// and the pre login page is served with /login mapping
}
}
Below is the filter to perform the logic you have mentioned
#WebFilter("/*")
public class AuthTokenFilter implements Filter {
#Override
public void destroy() {
// ...
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
//
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String xHeader = ((HttpServletRequest)request).getHeader("X-Auth-Token");
if(getPermission(xHeader)) {
chain.doFilter(request, response);
} else {
request.getRequestDispatcher("401.html").forward(request, response);
}
}
}
And you got it right, the spring config should be following.
public class MyWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Filter[] getServletFilters() {
return new Filter[]{new AuthTokenFilter()};
}
}
Spring can use filters, but they recommend that you use their version of filters, known as an interceptor
http://viralpatel.net/blogs/spring-mvc-interceptor-example/
There is a quick run through of how they work. They are nearly identical to filters, but designed to work inside the Spring MVC lifecycle.
I assume that you are trying to implement some kind of OAuth security which is based on jwt token.
Nowdays there are several ways to do so but here is my favourite one:
Here is how the filter looks like:
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.filter.GenericFilterBean;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureException;
public class JwtFilter extends GenericFilterBean {
#Override
public void doFilter(final ServletRequest req,
final ServletResponse res,
final FilterChain chain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) req;
final String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new ServletException("Missing or invalid Authorization header.");
}
final String token = authHeader.substring(7); // The part after "Bearer "
try {
final Claims claims = Jwts.parser().setSigningKey("secretkey")
.parseClaimsJws(token).getBody();
request.setAttribute("claims", claims);
}
catch (final SignatureException e) {
throw new ServletException("Invalid token.");
}
chain.doFilter(req, res);
}
}
Pretty simple there is the user controller also where you can find the login method:
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletException;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
#RestController
#RequestMapping("/user")
public class UserController {
private final Map<String, List<String>> userDb = new HashMap<>();
public UserController() {
userDb.put("tom", Arrays.asList("user"));
userDb.put("sally", Arrays.asList("user", "admin"));
}
#RequestMapping(value = "login", method = RequestMethod.POST)
public LoginResponse login(#RequestBody final UserLogin login)
throws ServletException {
if (login.name == null || !userDb.containsKey(login.name)) {
throw new ServletException("Invalid login");
}
return new LoginResponse(Jwts.builder().setSubject(login.name)
.claim("roles", userDb.get(login.name)).setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS256, "secretkey").compact());
}
#SuppressWarnings("unused")
private static class UserLogin {
public String name;
public String password;
}
#SuppressWarnings("unused")
private static class LoginResponse {
public String token;
public LoginResponse(final String token) {
this.token = token;
}
}
}
Of course we have Main where you can see the filter bean:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.embedded.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
#EnableAutoConfiguration
#ComponentScan
#Configuration
public class WebApplication {
#Bean
public FilterRegistrationBean jwtFilter() {
final FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new JwtFilter());
registrationBean.addUrlPatterns("/api/*");
return registrationBean;
}
public static void main(final String[] args) throws Exception {
SpringApplication.run(WebApplication.class, args);
}
}
Last but not least there is an example controller:
import io.jsonwebtoken.Claims;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/api")
public class ApiController {
#SuppressWarnings("unchecked")
#RequestMapping(value = "role/{role}", method = RequestMethod.GET)
public Boolean login(#PathVariable final String role,
final HttpServletRequest request) throws ServletException {
final Claims claims = (Claims) request.getAttribute("claims");
return ((List<String>) claims.get("roles")).contains(role);
}
}
Here is a link to GitHub all thanks goes to nielsutrecht for the great work I have used this project as base and it works perfectly.
You can also implement it using an aspect with a pointcut that targets a certain annotation. I have written a library that enables you to use annotations that perform authorization checks based on a JWT token.
You can find the project with all the documentation on: https://github.com/nille85/jwt-aspect. I have used this approach multiple times in order to secure a REST Backend that is consumed by a single page application.
I have also documented on my blog how you can use it in a Spring MVC Application: http://www.nille.be/security/creating-authorization-server-using-jwts/
The following is an extract from the example project on https://github.com/nille85/auth-server
The example underneath contains a protected method getClient. The annotation #Authorize that the aspect uses checks if the value from the "aud jwt claim" matches the clientId parameter that is annotated with #ClaimValue. If it matches, the method can be entered. Otherwise an exception is thrown.
#RestController
#RequestMapping(path = "/clients")
public class ClientController {
private final ClientService clientService;
#Autowired
public ClientController(final ClientService clientService) {
this.clientService = clientService;
}
#Authorize("hasClaim('aud','#clientid')")
#RequestMapping(value = "/{clientid}", method = RequestMethod.GET, produces = "application/json")
#ResponseStatus(value = HttpStatus.OK)
public #ResponseBody Client getClient(#PathVariable(value = "clientid") #ClaimValue(value = "clientid") final String clientId) {
return clientService.getClient(clientId);
}
#RequestMapping(value = "", method = RequestMethod.GET, produces = "application/json")
#ResponseStatus(value = HttpStatus.OK)
public #ResponseBody List<Client> getClients() {
return clientService.getClients();
}
#RequestMapping(path = "", method = RequestMethod.POST, produces = "application/json")
#ResponseStatus(value = HttpStatus.OK)
public #ResponseBody Client registerClient(#RequestBody RegisterClientCommand command) {
return clientService.register(command);
}
}
The Aspect itself can be configured like:
#Bean
public JWTAspect jwtAspect() {
JWTAspect aspect = new JWTAspect(payloadService());
return aspect;
}
The PayloadService that is needed can for example be implemented like:
public class PayloadRequestService implements PayloadService {
private final JWTVerifier verifier;
public PayloadRequestService(final JWTVerifier verifier){
this.verifier = verifier;
}
#Override
public Payload verify() {
ServletRequestAttributes t = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = t.getRequest();
final String jwtValue = request.getHeader("X-AUTH");
JWT jwt = new JWT(jwtValue);
Payload payload =verifier.verify(jwt);
return payload;
}
}
You can create and configure your own filter by doing following steps.
1) Create your class by implementing the filter interface and override its methods.
public class MyFilter implements javax.servlet.Filter{
public void destroy(){}
public void doFilter(Request, Response, FilterChain){//do what you want to filter
}
........
}
2) Now configure your filter in web.xml
<filter>
<filter-name>myFilter</filter-name>
<filter-class>MyFilter</filter-class>
</filter>
3) Now provide url mapping of the filter.
<filter-mapping>
<filter-name>myFilter</filter-name>
<url-pattern>*</url-pattern>
</filter-mapping>
4) Now restart your server and check all the web request will first come to MyFilter and then proceed to the respective controller.
Hopefully it will be the required answer.
Your approach looks correct.
Once I have used something similar to following (Removed most of the lines and kept it simple).
public class MvcDispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
EnumSet<DispatcherType> dispatcherTypes = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.ERROR);
FilterRegistration.Dynamic monitoringFilter = servletContext.addFilter("monitoringFilter", MonitoringFilter.class);
monitoringFilter.addMappingForUrlPatterns(dispatcherTypes, false, "/api/admin/*");
}
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { WebMvcConfig.class };
}
#Override
protected Class<?>[] getServletConfigClasses() {
return null;
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
Also you need a custom filter looks like below.
public class CustomXHeaderFilter implements Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String xHeader = request.getHeader("X-Auth-Token");
if(YOUR xHeader validation fails){
//Redirect to a view
//OR something similar
return;
}else{
//If the xHeader is OK, go through the chain as a proper request
chain.doFilter(request, response);
}
}
#Override
public void destroy() {
}
#Override
public void init(FilterConfig arg0) throws ServletException {
}
}
Hope this helps.
Additionally you can use FilterRegistrationBean if you Spring Boot. It does the same thing (I think so) which FilterRegistration.Dynamic does.