Implementing own authentication in spring - java

I want to implement my own authentication with spring. To keep things simple at first I'm going to implement the first step without any session, but an HTTP-Authorization header sent in every request.
I've read the documentation, many tutorials and of course searched on stackoverflow, but I couldn't fix it.
What I have is a filter (RequestFilter), which extracts the Authorization header and initializes the security context with an own Authentication (AuthenticationToken). Then there is an AuthenticationProvider (TokenAuthenticationProvider) supporting my AuthenticationToken. The filter is working, the authentication provider is not. None of the methods in TokenAuthenticationProvider are invoked. Can you help me, thanks :).
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final Logger LOGGER = Logger.getLogger(SecurityConfig.class.getName());
#Autowired
TokenAuthenticationProvider tokenAuthenticationProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(new RequestFilter(), BasicAuthenticationFilter.class)
.csrf().disable()
;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(tokenAuthenticationProvider)
;
}
}
#Component
public class TokenAuthenticationProvider implements AuthenticationProvider {
private static final Logger LOGGER = Logger.getLogger(TokenAuthenticationProvider.class.getName());
#Autowired
ClientRepository clientRepository;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
final String authToken = authentication.getCredentials().toString();
LOGGER.severe("AUTH TOKEN: " + authToken);
return Optional.ofNullable(clientRepository.findByAuthToken(authToken))
.map((Client client) -> new AuthenticationToken(client.getId(), client.getAuthToken()))
.orElseThrow(() -> new AccessDeniedException(""));
}
#Override
public boolean supports(Class<?> authentication) {
LOGGER.severe(authentication.getName());
return AuthenticationToken.class.isAssignableFrom(authentication);
}
}
public class RequestFilter extends OncePerRequestFilter {
private final Logger LOGGER = Logger.getLogger(RequestFilter.class.getName());
#Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
LOGGER.severe("RequestFilter works");
Optional.ofNullable(request.getHeader("Authorization"))
.ifPresent((String token) -> SecurityContextHolder
.getContext()
.setAuthentication(new AuthenticationToken(token))
);
chain.doFilter(request, response);
}
}
public class AuthenticationToken extends AbstractAuthenticationToken {
private final String credentials;
private final Long principal;
public AuthenticationToken(String credentials) {
this(null, credentials);
setAuthenticated(false);
}
public AuthenticationToken(Long principal, String credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(true);
}
#Override
public Object getCredentials() {
return credentials;
}
#Override
public Object getPrincipal() {
return principal;
}
}
#RestController
public class HttpGateController {
private static final Logger LOGGER = Logger.getLogger(HttpGateController.class.getName());
#RequestMapping(
name="/gate",
method= RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE
)
public String gateAction(#RequestBody String request) {
Optional.ofNullable(SecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.map(Authentication::getCredentials)
.map(ObjectUtils::nullSafeToString)
.ifPresent(LOGGER::severe);
return request;
}
#RequestMapping("/status")
public String statusAction() {
return "It works.";
}
}

Related

Spring custom auth filter and custom jwtfilter

What I'm trying to implement is to make my multitenant app workspace aware. With this I mean that besides username and password, I'm validating the workspace as well.
Before, I had (working) normal authentication (username and password) and a JWTFilter that is a OncePerRequestFilter.
What I did?
Extended UsernamePasswordAuthenticationToken: just to add the workspace
Extended AbstractUserDetailsAuthenticationProvider: defining my customPasswordEncoder and customUserDetailsService
Made a CustomUserDetailsService: instead of the loadByUsername I've made a loadUserByWorkspaceAndUsername
Configured the WebSecurity for the new extended classes
The outcome is always unauthorized :(
What I've tried?
While debugging the code never pass on the CustomAuthenticationFilter and that's the reason I'm focusing my efforts there. Really doesn't know what I'm doing wrong here. If you need any further information please shout.
Replacing the UsernamePasswordAuthenticationFilter using the addFilter(authenticationFilter())
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(authenticationFilter(), JwtFilter.class);
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(authenticationFilter(),UsernamePasswordAuthenticationFilter.class);
.addFilterBefore(authenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(jwtAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class);
A bit of code.
CustomAuthenticationToken
public class CustomAuthenticationToken extends UsernamePasswordAuthenticationToken {
private String workspace;
public CustomAuthenticationToken(final Object principal,
final Object credentials,
final String workspace) {
super(principal, credentials);
this.workspace = workspace;
}
public CustomAuthenticationToken(final Object principal,
final Object credentials,
final String workspace, Collection<? extends GrantedAuthority> authorities) {
super(principal, credentials, authorities);
this.workspace = workspace;
super.setAuthenticated(true);
}
public String getWorkspace() {
return this.workspace;
}
}
CustomAuthenticationFilter
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private static final String SPRING_SECURITY_FORM_DOMAIN_KEY = "workspace";
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
if (!request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: "
+ request.getMethod());
}
CustomAuthenticationToken authRequest = getAuthRequest(request);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
private CustomAuthenticationToken getAuthRequest(HttpServletRequest request) {
String username = obtainUsername(request);
String password = obtainPassword(request);
String domain = obtainDomain(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
if (domain == null) {
domain = "";
}
username = username.trim();
return new CustomAuthenticationToken(username, password, domain);
}
private String obtainDomain(HttpServletRequest request) {
return request.getParameter(SPRING_SECURITY_FORM_DOMAIN_KEY);
}
}
CustomUserDetailsAuthenticationProvider
#Component
public class CustomUserDetailsAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
/**
* The plaintext password used to perform
* PasswordEncoder#matches(CharSequence, String)} on when the user is
* not found to avoid SEC-2056.
*/
private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
private final PasswordEncoder customPasswordEncoder;
private final CustomUserDetailsService customUserDetailsService;
private String userNotFoundEncodedPassword;
public CustomUserDetailsAuthenticationProvider(final PasswordEncoder customPasswordEncoder,
final CustomUserDetailsService customUserDetailsService) {
this.customPasswordEncoder = customPasswordEncoder;
this.customUserDetailsService = customUserDetailsService;
}
#Override
protected void additionalAuthenticationChecks(final UserDetails userDetails,
final UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
final String presentedPassword = authentication.getCredentials().toString();
if (!customPasswordEncoder.matches(presentedPassword, userDetails.getPassword())) {
logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
#Override
protected UserDetails retrieveUser(final String username,
final UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
final CustomAuthenticationToken auth = (CustomAuthenticationToken) authentication;
UserDetails loadedUser;
try {
loadedUser = this.customUserDetailsService.loadUserByWorkspaceAndUsername(auth.getWorkspace(), auth.getPrincipal().toString());
} catch (UsernameNotFoundException notFound) {
if (authentication.getCredentials() != null) {
String presentedPassword = authentication.getCredentials().toString();
customPasswordEncoder.matches(presentedPassword, userNotFoundEncodedPassword);
}
throw notFound;
} catch (Exception repositoryProblem) {
throw new InternalAuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem);
}
if (loadedUser == null) {
throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
#Override
protected void doAfterPropertiesSet() throws Exception {
Assert.notNull(this.customUserDetailsService, "A UserDetailsService must be set");
this.userNotFoundEncodedPassword = this.customPasswordEncoder.encode(USER_NOT_FOUND_PASSWORD);
}
}
CustomUserDetailsServiceImpl
#Component
public class CustomUserDetailsServiceImpl implements CustomUserDetailsService {
private static final Logger LOGGER = LoggerFactory.getLogger(com.cliwise.security.workspace.CustomUserDetailsServiceImpl.class);
private final LoginAttemptService loginAttemptService;
private final UserRepository userRepository;
private final HttpServletRequest request;
public CustomUserDetailsServiceImpl(LoginAttemptService loginAttemptService, UserRepository userRepository, HttpServletRequest request) {
this.loginAttemptService = loginAttemptService;
this.userRepository = userRepository;
this.request = request;
}
#Override
public UserDetails loadUserByWorkspaceAndUsername(String workspace, String username) throws UsernameNotFoundException {
final User user = userRepository.findByUsernameOrEmailAndWorkspace(username, username, workspace)
.orElseThrow(() -> new UserNotFoundException("User not found with username or email : " + username));
return UserPrincipal.create(user);
}
}
The last but no least
WebSecurity
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
private final CustomAuthenticationEntryPoint unauthorizedHandler;
private final CustomUserDetailsAuthenticationProvider customUserDetailsAuthenticationProvider;
public WebSecurity(final CustomAuthenticationEntryPoint unauthorizedHandler,
final CustomUserDetailsAuthenticationProvider customUserDetailsAuthenticationProvider) {
this.unauthorizedHandler = unauthorizedHandler;
this.customUserDetailsAuthenticationProvider = customUserDetailsAuthenticationProvider;
}
#Override
public void configure(final AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.authenticationProvider(customUserDetailsAuthenticationProvider);
}
#Bean
public CustomAuthenticationFilter authenticationFilter() throws Exception {
CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
filter.setAuthenticationManager(authenticationManagerBean());
return filter;
}
#Bean(BeanIds.AUTHENTICATION_MANAGER)
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.httpBasic().disable()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
.and()
.exceptionHandling().accessDeniedHandler(accessDeniedHandler())
.and()
.addFilterBefore(jwtAuthenticationFilter(), CustomAuthenticationFilter.class);
http
.authorizeRequests()
.antMatchers("/auth").permitAll()
.anyRequest()
.authenticated();
}
#Bean
public AccessDeniedHandler accessDeniedHandler() {
return new CustomAccessDeniedHandler();
}
#Bean
public JwtFilter jwtAuthenticationFilter() {
return new JwtFilter();
}
}
Thanks in advance for your time.
My understanding is that you are facing the problem in CustomUserDetailsAuthenticationProvider. Since you are extending AbstractUserDetailsAuthenticationProver you will get a default implementation for
public Authentication authenticate(Authentication authentication)
throws AuthenticationException;
See if its properly authenticating the authentication object, if not you will have to override the method and write your own implementation.

Spring Security's permitAll doesn't work for certain endpoints

I have
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/api/v1/account/import").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
I want that all users can come to /api/v1/account/import without any JWT token check. For all other endpoints I want a JWT token check in class JWTAuthenticationFilter. I tried many different scenarios but all failed. I always get to JWTAuthenticationFilter. I don't want to get to JWTAuthenticationFilter if I go to /api/v1/account/import.
My controller:
#RestController
#RequestMapping(value = "/api/v1/account")
public class AccountController {
private final AccountService accountService;
public AccountController(final AccountService accountService) {
this.accountService = accountService;
}
#PostMapping(path = "/import")
#ResponseStatus(HttpStatus.ACCEPTED)
public String importAccount(#Valid #RequestBody final ImportAccountDto importAccountDto) {
return this.accountService.importAccount(importAccountDto);
}
My JWT filter:
public class JWTAuthenticationFilter extends GenericFilterBean {
#Override
public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain filterChain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) req;
final HttpServletResponse response = (HttpServletResponse) res;
final String token = request.getHeader("Authorization");
final JJWTService jjwtService = new JJWTService();
if (token == null || !jjwtService.parseJWTToken(token)) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
} else {
filterChain.doFilter(req, res);
}
}
My test:
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class AccountIT {
#Autowired
MockMvc mockMvc;
#Autowired
private AccountRepository accountRepository;
#Test
public void importAccount() throws Exception {
this.mockMvc.perform(post("/api/v1/account/import")
.contentType(MediaType.APPLICATION_JSON)
.content(toJson(importAccountDto)))
.andExpect(status().isAccepted())
.andReturn();
}
Try this
if (!request.getRequestURI().contains("/api/v1/account/import")) {
final JJWTService jjwtService = new JJWTService();
if (token == null || !jjwtService.parseJWTToken(token)) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
} else {
filterChain.doFilter(req, res);
}
}

Endpoint Response got interchanged in spring boot application

I have a spring boot application i have many endpoint in this application. When i am hitting endpoint simultaneously JSON response from two different endpoint got interchanged.
For example:
i am hitting /currency/list endpoint and /fee endpoint and i am getting /fee endpoint data in currency/list endpoint and vice versa.
I have no idea why this happening. If anybody can suggest why happening will be helpful.
Also I am using spring security token based auth in this project
CurrencyController.java
#RestController
#RequestMapping(value = UrlConstant.BASE_ADMIN_URI_V1)
#Api(value = "Admin Controller")
#Scope("request")
public class CurrencyController {
public static final Logger logger = LoggerFactory.getLogger(CurrencyController.class);
#Autowired
private LocaleService localService;
#RequestMapping(value = UrlConstant.CURRENCY_LIST_FOR_MARKET, method = RequestMethod.GET)
public ResponseEntity<Object> getCurrencyListForMarket() {
List<Currency> currencyList = currencyService.getCurrencyListForMarket();
ObjectMapper mapper = new ObjectMapper();
try {
String stringList = mapper.writeValueAsString(currencyList);
logger.debug("all currency list as String: {}", stringList);
} catch (JsonProcessingException e) {
logger.debug("error in currency list: {}", e.getMessage());
e.printStackTrace();
}
return ResponseHandler.response(HttpStatus.OK, false, localService.getMessage("currency.list.success"),
currencyList);
}
}
AdminController.java
#RestController
#RequestMapping(value = UrlConstant.BASE_ADMIN_URI_V1)
#Api(value = "Admin Controller")
#Scope("request")
public class AdminController {
#Autowired
private LocaleService localeService;
#Autowired
private FeeService feeService;
#RequestMapping(value = UrlConstant.TRADING_FEES, method = RequestMethod.GET)
public ResponseEntity<Object> getTradingFees() {
TradingFee fee = tradingFeeService.getTradingFee();
return ResponseHandler.response(HttpStatus.OK, true,
localeService.getMessage("admin.transaction.fees.found.success"), fee);
}
}
TokenAuthenticationFilter.java
public class TokenAuthenticationFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
final HttpServletRequest httpRequest = (HttpServletRequest) request;
// extract token from header
String token = httpRequest.getHeader("Authorization");
if (token != null && !token.isEmpty()) {
AuthenticationTokenRepo authenticationTokenRepository = WebApplicationContextUtils
.getRequiredWebApplicationContext(httpRequest.getServletContext())
.getBean(AuthenticationTokenRepo.class);
// check whether token is valid
AuthenticationToken authToken = authenticationTokenRepository.findByToken(token);
if (authToken != null) {
// Add user to SecurityContextHolder
final UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
authToken.getUser(), null, new ApplicationUserDetail(authToken.getUser()).getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(request, response);
SecurityContextHolder.clearContext();
}
SecurityConfig.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private Environment environment;
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests().antMatchers(HttpMethod.POST, "/api/v1/login").permitAll()
.antMatchers(HttpMethod.POST, "/api/v1/user/register").permitAll().anyRequest().authenticated();
// Implementing Token based authentication in this filter
final TokenAuthenticationFilter tokenFilter = new TokenAuthenticationFilter();
http.addFilterBefore(tokenFilter, BasicAuthenticationFilter.class);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(UrlConstant.BASE_ADMIN_URI_V1 + UrlConstant.CURRENCY_LIST_FOR_MARKET);
web.ignoring().antMatchers(UrlConstant.BASE_ADMIN_URI_V1 + UrlConstant.TRADING_FEES);
}
}
}
ApplicationUserDetail.java
public class ApplicationUserDetail implements UserDetails,Serializable {
private static final long serialVersionUID = 1L;
transient User user;
public ApplicationUserDetail(User user) {
this.user = user;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRole().getName());
}
#Override
public String getPassword() {
return user.getPassword();
}
#Override
public String getUsername() {
return user.getEmailId();
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return user.getIsEnabled();
}
}
More info: When i am printing response in my api it is correct but when i am printing it in my Authentication filter i got the response of fee api in my currency api so i think there is some problem between api to filter.

how to ignore spring security CSRF for specific URL's in spring boot project

how can I ignore CSRF security for specific URL which is like "/workflow/**".
Except for this URL, I need both authorization and CSRF security for all the URL's and methods.
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private RESTAuthenticationEntryPoint authenticationEntryPoint;
#Autowired
private RESTAuthenticationFailureHandler authenticationFailureHandler;
#Autowired
private RESTAuthenticationSuccessHandler authenticationSuccessHandler;
#Autowired
private PranaUserDetailsService userDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().requireCsrfProtectionMatcher(new AllExceptUrlStartedWith("/workflow"))
.and().authorizeRequests()
.antMatchers("/rest/**", "/tasklist").authenticated()
.and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/index.html")
.and().exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
.and().formLogin().successHandler(authenticationSuccessHandler)
.and().formLogin().failureHandler(authenticationFailureHandler)
.and().csrf().csrfTokenRepository(csrfTokenRepository()).and().addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class);
}
private static class AllExceptUrlStartedWith implements RequestMatcher {
private static final String[] ALLOWED_METHODS =
new String[] {"GET"};
private final String[] allowedUrls;
public AllExceptUrlStartedWith(String... allowedUrls) {
this.allowedUrls = allowedUrls;
}
#Override
public boolean matches(HttpServletRequest request) {
String method = request.getMethod();
for(String allowedMethod : ALLOWED_METHODS) {
if (allowedMethod.equals(method)) {
return false;
}
}
String uri = request.getRequestURI();
for (String allowedUrl : allowedUrls) {
if (uri.startsWith(allowedUrl)) {
return false;
}
}
return true;
}
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/styles/**").antMatchers("/scripts/**");
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
}
how can I ignore CSRF security for specific URL which is like "/workflow/**".
Except for this URL, I need both authorization and CSRF security for all the URL's and methods.
In my project I'm using the following code:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
...
.csrf()
// Allow unsecured requests to H2 console
.requireCsrfProtectionMatcher(new AllExceptUrlsStartedWith("/console"))
...
}
private static class AllExceptUrlsStartedWith implements RequestMatcher {
private static final String[] ALLOWED_METHODS =
new String[] {"GET", "HEAD", "TRACE", "OPTIONS"};
private final String[] allowedUrls;
public AllExceptUrlsStartedWith(String... allowedUrls) {
this.allowedUrls = allowedUrls;
}
#Override
public boolean matches(HttpServletRequest request) {
// replicate default behavior (see CsrfFilter.DefaultRequiresCsrfMatcher class)
String method = request.getMethod();
for (String allowedMethod : ALLOWED_METHODS) {
if (allowedMethod.equals(method)) {
return false;
}
}
// apply our own exceptions
String uri = request.getRequestURI();
for (String allowedUrl : allowedUrls) {
if (uri.startsWith(allowedUrl)) {
return false;
}
}
return true;
}
}
In this example I've disabled CSRF protection for /console.
Update: since Spring Security 4.0 you can simplify it to a single line:
csrf()
.ignoringAntMatchers("/nocsrf","/ignore/startswith/**")
Only one purpose of answering in this thread is to explain and use antPathMatcher whose advantages can be taken to protect many urls with ant matchers.
From Doc
.csrf().requireCsrfProtectionMatcher(RequestMatcher requireCsrfProtectionMatcher)
Specify the RequestMatcher to use for determining when CSRF should be applied. The default is to ignore GET, HEAD, TRACE, OPTIONS and process all other requests.
Note that by default GET, HEAD, TRACE, OPTIONS requests are ignored. If you want to override this defaults configure requireCsrfProtectionMatcher(implementation_of_RequestMatcher).
In implementation of RequestMatcher define all the URL's which needs to be protected. You are done
Say you want URL's /api/** to be ensured for CSRF protection.
#Autowired
RequestMatcher csrfProtectedMatchers;
#Override
protected void configure(final HttpSecurity http) throws Exception
{
http
.authorizeRequests()
.antMatchers("/resources/**", "/", "/login").permitAll()
.antMatchers("/api/**").hasAnyRole("ADMIN", "USER")
.antMatchers("/app/user/*")
.hasAnyRole("ADMIN", "USER")
.and().formLogin()
.and().csrf().requireCsrfProtectionMatcher(csrfProtectedMatchers);
}
#Bean
public RequestMatcher getCsrfProtectedMatchers()
{
UrlPathHelper urlPathHelper = new UrlPathHelper();
AntPathMatcher antPathMatcher = new AntPathMatcher();
List<String> protectedUrlPatterns = Arrays.asList("/api/**", "/logout");
return new RequestMatcher()
{
#Override
public boolean matches(HttpServletRequest request)
{
String uri = urlPathHelper.getPathWithinApplication(request);
for (String pattern : protectedUrlPatterns)
{
if (antPathMatcher.match(pattern, uri))
{
return true;
}
}
return false;
}
};
}
Logic explained
suppose URL: http://localhost:8080/csrf/api/test1
String uri = urlPathHelper.getPathWithinApplication(request);
uri => /api/test1;
antPathMatcher.match("/api/**", "/api/test1") => true
answer for my own question... thanks to #Slava
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private RESTAuthenticationEntryPoint authenticationEntryPoint;
#Autowired
private RESTAuthenticationFailureHandler authenticationFailureHandler;
#Autowired
private RESTAuthenticationSuccessHandler authenticationSuccessHandler;
#Autowired
private PranaUserDetailsService userDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().requireCsrfProtectionMatcher(new AllExceptUrlStartedWith("/workflow"))
.and().authorizeRequests()
.antMatchers("/rest/**", "/tasklist").authenticated()
.and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/index.html")
.and().exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
.and().formLogin().successHandler(authenticationSuccessHandler)
.and().formLogin().failureHandler(authenticationFailureHandler)
.and().csrf().csrfTokenRepository(csrfTokenRepository()).and().addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class);
}
private static class AllExceptUrlStartedWith implements RequestMatcher {
private static final String[] ALLOWED_METHODS =
new String[] {"GET"};
private final String[] allowedUrls;
public AllExceptUrlStartedWith(String... allowedUrls) {
this.allowedUrls = allowedUrls;
}
#Override
public boolean matches(HttpServletRequest request) {
String method = request.getMethod();
for(String allowedMethod : ALLOWED_METHODS) {
if (allowedMethod.equals(method)) {
return false;
}
}
String uri = request.getRequestURI();
for (String allowedUrl : allowedUrls) {
if (uri.startsWith(allowedUrl)) {
return false;
}
}
return true;
}
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/styles/**").antMatchers("/scripts/**");
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
}

Jersey: Use Provider in Resource Filter

Using Jersey 1.14 and Spring 3.1.2
I want to create a filter like this: https://gist.github.com/3031495
but in that filter I want access to a provider I created.
I'm getting an IllegalStateException. I suspect something in my lifecycle is hosed up. I can access #Context private HttpServletRequest and pull the session info I need from there, but then two classes have to know about where/how to get my "AuthUser" object.
Any help is appreciated!
My Provider:
#Component
#Provider
public class AuthUserProvider extends AbstractHttpContextInjectable<AuthUser> implements
InjectableProvider<Context, Type> {
private static final Logger LOG = LoggerFactory.getLogger(AuthUserProvider.class);
#Context
HttpServletRequest req;
public void init() {
LOG.debug("created");
}
#Override
// this may return a null AuthUser, which is what we want....remember, a
// null AuthUser means the user hasn't authenticated yet
public AuthUser getValue(HttpContext ctx) {
return (AuthUser) req.getSession().getAttribute(AuthUser.KEY);
}
// InjectableProvider implementation:
public ComponentScope getScope() {
return ComponentScope.Singleton;
}
public Injectable<AuthUser> getInjectable(ComponentContext ic, Context ctx, Type c) {
if (AuthUser.class.equals(c)) {
return this;
}
return null;
}
}
My Filter:
#Component
public class TodoFilter implements ResourceFilter {
private static final Logger LOG = LoggerFactory.getLogger(TodoFilter.class);
#Autowired
private JdbcTemplate todoTemplate;
// this works
#Context
private HttpServletRequest servletRequest;
// this throws a java.lang.IllegalStateException
// #Context
// private AuthUser authUser;
public void init() throws Exception {
LOG.debug("created");
LOG.debug(todoTemplate.getDataSource().getConnection().getMetaData()
.getDatabaseProductName());
}
#Override
public ContainerRequestFilter getRequestFilter() {
return new ContainerRequestFilter() {
#Override
public ContainerRequest filter(ContainerRequest request) {
LOG.debug("checking if {} is authorized to use {}", "my authenticated user",
request.getPath());
// String name = request.getUserPrincipal().getName();
// String[] admins = settings.getAdminUsers();
// for (String adminName : admins) {
// if (adminName.equals(name))
// return request;
// }
// if (authUser.getUsername().equals("jberk")) {
// return request;
// }
// return HTTP 403 if name is not found in admin users
throw new WebApplicationException(Response.status(Response.Status.FORBIDDEN)
.entity("You are not authorized!").build());
}
};
}
#Override
public ContainerResponseFilter getResponseFilter() {
return new ContainerResponseFilter() {
#Override
public ContainerResponse filter(ContainerRequest request,
ContainerResponse response) {
// do nothing
return response;
}
};
}
}
My Service (aka Resource):
#Component
#Path("/rs/todo")
#Produces(MediaType.APPLICATION_JSON)
#ResourceFilters(TodoFilter.class)
public class TodoService {
#GET / #POST methods
}
so I think I figured this out....
I added this to my ResourceFilter:
#Context
private HttpContext ctx;
#Autowired
private AuthUserProvider provider;
then I can do this in the filter method:
public ContainerRequest filter(ContainerRequest request) {
AuthUser authUser = provider.getValue(ctx);
// use authuser in some way
}
this might not be "correct"...but it's working and I don't have code duplication
public ComponentScope getScope() {
return ComponentScope.Singleton;
}
It should be
public ComponentScope getScope() {
return ComponentScope.PerRequest;
}

Categories

Resources