This is how my websecurityconfig is described:
public void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(corsFilter(), SessionManagementFilter.class) //adds your custom CorsFilter
.authorizeRequests()
.antMatchers("/ping/get").permitAll()
.antMatchers("/user/updatePassword").permitAll()
.antMatchers("/user/resetPassword").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.successHandler(successHandler())
.failureHandler(failureHandler())
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler())
.authenticationEntryPoint(authenticationEntryPoint())
.and()
.logout()
.logoutSuccessUrl("/login")
.invalidateHttpSession(true)
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessHandler((httpServletRequest, httpServletResponse, authentication) -> {
httpServletResponse.setStatus(HttpServletResponse.SC_OK);
})
.permitAll()
;
http.csrf().disable();
http.headers()
.addHeaderWriter(
new StaticHeadersWriter("Access-Control-Allow-Origin", "http://localhost:4200")
);
http.headers()
.addHeaderWriter(
new StaticHeadersWriter("Access-Control-Allow-Credentials", "true")
);
}
and the cors filter i've defined the header for samesite=none as follows
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletResponse res = (HttpServletResponse) servletResponse;
HttpServletRequest req= (HttpServletRequest) servletRequest;
res.setHeader("Access-Control-Allow-Origin", req.getHeader("origin"));
res.setHeader("Access-Control-Allow-Credentials", "true");
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
res.setHeader("Access-Control-Max-Age", "3600");
res.setHeader("Access-Control-Allow-Headers", "access-control-allow-origin,content-type");
res.addHeader("Access-Control-Expose-Headers", "xsrf-token");
res.setHeader("Set-Cookie", "HttpOnly;Secure;SameSite=None");
if ("OPTIONS".equals(req.getMethod())) {
res.setStatus(HttpServletResponse.SC_OK);
} else {
filterChain.doFilter(req, res);
}
}
#Override
public void destroy() {
}
}
But whenever i'm calling the login endpoint, i'm only receiving httponly,secure but nnot samesite=none with my JSESSIONID cookkie. How do i make this work?
i've tried all different filter from answers of other questons but none of them work.
The issue is only on chrome. It would be helpful if there is any workaroud also. Recently chrome removed the samesite flag which could be disabled. need to solve this in order to create a webview for my site.
You can set the SameSite attribute when using Spring Session with a custom CookieSerializer.
#Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName("JSESSIONID");
serializer.setSameSite("None");
serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$");
return serializer;
}
Related
I want to use Spring security to perform authentication and authorization on 2 separated server.
I've authenticated successfully and received a JWT token on the first server.
Now I am sending a request to the second server with the JWT authorization token, but the server can't see it; basically getHeader() from getJwtFromRequest(HttpServletRequest request) returns null.
This is the server code:
//imports...
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(
securedEnabled = true,
jsr250Enabled = true,
prePostEnabled = true
)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
CustomUserDetailsService customUserDetailsService;
#Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
#Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
#Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.userDetailsService(customUserDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean(BeanIds.AUTHENTICATION_MANAGER)
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/",
"/favicon.ico",
"/**/*.png",
"/**/*.gif",
"/**/*.svg",
"/**/*.jpg",
"/**/*.html",
"/**/*.css",
"/**/*.js")
.permitAll()
.antMatchers("/api/auth/**")
.permitAll()
.antMatchers("/api/user/checkUsernameAvailability", "/api/user/checkEmailAvailability")
.permitAll()
.antMatchers(HttpMethod.GET, "/api/polls/**", "/api/users/**")
.permitAll()
.anyRequest()
.authenticated();
// Add our custom JWT security filter
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
//imports...
public class JwtAuthenticationFilter extends OncePerRequestFilter {
#Autowired
private JwtTokenProvider tokenProvider;
#Autowired
private CustomUserDetailsService customUserDetailsService;
private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class);
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
String jwt = getJwtFromRequest(request);
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
Long userId = tokenProvider.getUserIdFromJWT(jwt);
UserDetails userDetails = customUserDetailsService.loadUserById(userId);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception ex) {
logger.error("Could not set user authentication in security context", ex);
}
filterChain.doFilter(request, response);
}
private String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
// bearerToken prints null!
System.out.println("AUTHORIZATION: "+bearerToken);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7, bearerToken.length());
}
return null;
}
}
But the token is present on the request:
The authorization header was stripped on the edge server
#Autowired
private BCryptPasswordEncoder passwordEncoder;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(cUserDetailService)
.passwordEncoder(passwordEncoder);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/user/**").authenticated()
.and()
.csrf().disable()
.formLogin()
.usernameParameter("username")
.passwordParameter("password")
.successHandler(successHandler)
.failureHandler(failureHandler)
.and()
.logout()
.logoutSuccessHandler(logoutSuccessHandler)
.and()
.exceptionHandling()
.authenticationEntryPoint(authEntryPoint)
.accessDeniedHandler(accessDeniedHandler)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.maximumSessions(1)
.maxSessionsPreventsLogin(false);
}
public class CUserDetailService implements UserDetailsService {
#Autowired
private UserDetailDataEntityRepository userDetailDataEntityRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("username: " + username + ", " );
log.info(username == null);
Optional<UserDetailDataEntity> optional = userDetailDataEntityRepository.findByUsername(username);
if (optional.isPresent()) {
return optional.get();
} else {
throw new UsernameNotFoundException(username);
}
}
}
I used this config for my spring security, and got success login at first.
but then we find that if nobody access the server(even the login url) for a few days, and do login again the loadUserByUsername username will be empty and get login fail
FYI I used redis for session manage by adding dependency
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
and the login request is a form request
I need to implement spring JWT security for my end points.. I have 2 routes - 1 for internal and 2nd for external. I tried to add the code below but both my filters are executing for any requests..
I can add a logic in the filter based on the url.. But I didnt feel thats the right approach. Please let me know what would be the right approach and how to solve it?
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/internal/**")
.authenticated()
.and()
.addFilterBefore(jwtAuthenticationInternalFilter(), BasicAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/external/**")
.authenticated()
.and()
.addFilterBefore(jwtAuthenticationExternalFilter(), BasicAuthenticationFilter.class);
public class ExternalAuthenticationFilter extends OncePerRequestFilter {
#Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
System.out.println("Its hitting here - External");//GET THE Information and build Authentication object..
// SecurityContextHolder.getContext().setAuthentication(token);
filterChain.doFilter(request, response);
}
}
public class InternalAuthenticationFilter extends OncePerRequestFilter {
#Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
System.out.println("Its hitting here - Internal");//GET THE Information and build Authentication object..
// SecurityContextHolder.getContext().setAuthentication(token);
filterChain.doFilter(request, response);
}
}
Both internal and external code is executing for any request.
sample request
/internal/abc,
/external/xyz .. Both cases both filters are being called..
Please suggest
You can split your security settings into two different configuration classes and mark them with e.g. #Order(1) and #Order(2) annotations. One config will deal with the /internal endpoints and one with the /external. In the configure(HttpSecurity http) method, first specify which endpoints you wish to configure and then apply your settings.
See example of one config bellow, the second config will be anological:
#EnableWebSecurity
#Order(1)
public class ExternalEndpointsSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/internal/**")
.authorizeRequests()
.authenticated()
.and()
.addFilterBefore(jwtAuthenticationInternalFilter(), BasicAuthenticationFilter.class)
}
}
I have a Spring-cloud based micro-services application. I also have an API gateway in front of all these services.
I need to support two types of clients.
One of them can call my application using an authorization token (by calling /authorize for example). The token is basically the SESSION ID. All the servers share the session by using Spring Session Redis.
The second client can only send me http basic authentication (user:pass as authorization header).
In the case of the second client, I need to check if the user is already authenticated and has an active session in redis. I added filter before BasicAuthenticationFilter in my security configuration to check that.
If the user has an active session, I'm putting the SESSIONID in the header, and removing the authorization header from the request (I'm using a custom HttpServletRequest wrapper for that). My purpose was that from that point on, Spring will manage the request in the downstream micro-services as if it was sent with a SESSIONID. The reason for that is to avoid a very long login time (more than 1 second).
Here’s my issue: when spring checks if the SESSIONID exists, it checks the original request which doesnt have any sessionId.
Security configuration:
#Resource
#Qualifier("sessions")
private Map<String, String> sessions;
#Autowired
#Qualifier("httpSessionStrategy")
HttpSessionStrategy sessionStrategy;
#Override
protected void configure(HttpSecurity http) throws Exception {
// // #formatter:off
http
.addFilterBefore(setSessionIdInHeader(), BasicAuthenticationFilter.class)
.sessionManagement()
.and()
.exceptionHandling()
.authenticationEntryPoint(restEntryPoint())
.and()
.headers().addHeaderWriter(new StaticHeadersWriter("Server",""))
.and()
.httpBasic()
.authenticationEntryPoint(restEntryPoint())
.and()
.logout().addLogoutHandler(clearTicketOnLogoutHandler())
.logoutSuccessHandler(logoutSuccessHandler())
.and()
.authorizeRequests()
.antMatchers("/index.html", "/login", "/").permitAll()
.antMatchers(HttpMethod.OPTIONS).denyAll()
.antMatchers(HttpMethod.HEAD).denyAll()
.anyRequest().authenticated()
.and()
.authenticationProvider(authenticationProvider)
.csrf()
.disable()
.addFilterAfter(ticketValidationFilter(), SessionManagementFilter.class)
.addFilterAfter(changePasswordFilter(), SessionManagementFilter.class)
.addFilterAfter(httpPolutionFilter(), SessionManagementFilter.class)
.addFilterAfter(saveSessionId(), SessionManagementFilter.class);
// #formatter:on
}
Filter to add header to request:
private Filter setSessionIdInHeader(){
return new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
Jedis jedis = null;
String authorization = request.getHeader("authorization");
String sessionId = null;
if (authorization != null){
if (sessions.get(authorization) != null){ //user already authenticated
sessionId = sessions.get(authorization);
jedis = getJedisPool().getResource();
if (jedis.hgetAll("spring:session:sessions:"+sessionId) != null){ //session alive in redis
log.info("session :"+ sessionId +" exists in redis");
HeaderMapRequestWrapper wrapper = new HeaderMapRequestWrapper(request);
wrapper.addHeader("TOKEN", sessionId);
wrapper.addHeader("mock_authorization", authorization);
filterChain.doFilter(wrapper, response);
}
}
}
filterChain.doFilter(request, response);
}
};
}
Change header name of SESSIONID:
#Bean
public HeaderHttpSessionStrategy httpSessionStrategy(){
HeaderHttpSessionStrategy headerHttpSessionStrategy = new HeaderHttpSessionStrategy();
headerHttpSessionStrategy.setHeaderName("TOKEN");
return headerHttpSessionStrategy;
}
private Filter saveSessionId() {
return new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if(request.getHeader("authorization") != null){
sessions.put(request.getHeader("authorization"), request.getSession().getId());
}else{
sessions.put(request.getHeader("mock_authorization"), request.getSession().getId());
}
}
};
}
I am using Spring boot 1.3.2 with Spring Security.
I have following configure(HttpSecurity http) method to inforce authentication
protected void configure(HttpSecurity http) throws Exception {
RequestMatcher csrfRequestMatcher = new RequestMatcher() {
private AntPathRequestMatcher[] requestMatchers = {
new AntPathRequestMatcher("/iams/w/*")
};
#Override
public boolean matches(HttpServletRequest request) {
for (AntPathRequestMatcher rm : requestMatchers) {
if (rm.matches(request)) { return true; }
}
return false;
} // method matches
};
http
.csrf()
.requireCsrfProtectionMatcher(csrfRequestMatcher)
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.requestCache()
.requestCache(new NullRequestCache())
.and()
.httpBasic();
}
and I have following configure(WebSecurity web) method to ignore some of the urls as below;
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(
"/myapp/docs/**",
"/myapp/docs/*",
"/myapp/docs/index.html",
"/resources/**",
"/static/**");
}
But http request to http://127.0.0.1:9000/myapp/docs/index.html still reuires username/password ( authentication ) and returns "status":401,"error":"Unauthorized"...
Actually none of the ignore url on WebSecurity is working since it also requires authentication. If I provide the auth then it works. How can I simply ignore some urls (like "/myapp/docs/**" ) here. I have following definition in the SecurityConfig class
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
What am I missing ? Please Advise.
It would probably be easier to use as simple a set of patterns as possible to leave unsecured, and then simply say that everything else IS secured.
This may be closer to what you want:
public static final String[] NOT_SECURED = {"/iams/docs/**","/static/**"};
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(NOT_SECURED);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(NOT_SECURED).permitAll()
.anyRequest().authenticated()
.and()
.httpBasic()
.and()
.requestCache()
.requestCache(new NullRequestCache())
.and()
.csrf().disable();
}
There is an error order in your code.
http
.csrf()
.requireCsrfProtectionMatcher(csrfRequestMatcher)
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.requestCache()
.requestCache(new NullRequestCache())
.and()
.httpBasic();
Therefore, any request is needed to be authenticated. You can directly use antMatchers.
http
.authorizeRequests()
.antMatchers("/iams/w/*")
.authenticated()
.and()
.httpBasic()
.and()
.requestCache()
.requestCache(new NullRequestCache())
.csrf().disable()
I hope it's helpful for you.
Thank you for your response but with your suggestion, my "/iams/w/*" is not protected at all. I can get to all these urls; "/iams/docs/**" , "/iams/w/" and "/iams/api/" without basic auth. Below is the set up as per your suggestion. Here I want to protect "/iams/w" and "/iams/api/" with username/password but let everyone get to "/iams/docs/*" without username/password. This is spring boot restful based implementation but want to expose some urls like docs so that it can be accessed by all and not the api calls.
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(
"/iams/docs/**",
"/iams/docs/*",
"/iams/docs/index.html",
"/static/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/iams/api/**","/iams/api/v1/*")
.authenticated()
.and()
.httpBasic()
.and()
.requestCache()
.requestCache(new NullRequestCache())
.and()
.csrf().disable();
}