We have a SpringBoot based module, we have REST APIs which allow creating resources with Params like
Request
POST /resources
{
"resourceName": "Res1",
"admins": ["john.doe#company.com", "jane.doe#company.com"]
}
Response
{
"id": "R1"
"resourceName": "Res1",
"admins": ["john.doe#company.com", "jane.doe#company.com"]
}
Request
POST /resources
{
"resourceName": "Res2",
"admins": ["alice#company.com", "bob#company.com"]
}
Response
{
"id": "R2"
"resourceName": "Res2",
"admins": ["alice#company.com", "bob#company.com"]
}
For R1 update API should only be accesible by John/Jane
Request
PUT /resources/R1
{
"resourceName": "Resource1",
"admins": ["john.doe#company.com", "jane.doe#company.com", "jacob#company.com"]
}
Response
For John / Jane the response should be:
{
"id": "R1"
"resourceName": "Resource1",
"admins": ["john.doe#company.com", "jane.doe#company.com", "jacob#company.com"]
}
When Alice / Bob user are updating R1 this response should be 403 Forbidden
Similarly For R2 update API should only be accesible by Alice / Bob.
When John / Jane are updating R2 this response should be 403 Forbidden
Please suggest which framework can be used to achieve this, preferably with less boiler plate
Currently we have a system where resource access is in for of RoleBasedAccessControl.
We achieve restriction by storing permissions. The RBAC config is saved in DB.
But now we need more fine grained control per resource which can be managed directly by existing admins
You could use Spring Security for this use case.
As a general practice it is recommended to assign roles for users for maintability.
For example assign admin role to - john.doe#company.com, jane.doe#company.com then manage the access using the role.
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
// Roles for users
private static final String ROLE_1 = "ADMIN";
private static final String ROLE_2 = "USER";
// In-memory users with roles
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("john.doe#company.com")
.password(passwordEncoder().encode("admin#123"))
.roles(ROLE_1)
.and()
.withUser("jane.doe#company.com")
.password(passwordEncoder().encode("admin#12345"))
.roles(ROLE_1)
.and()
.withUser("user")
.password(passwordEncoder().encode("user#123"))
.roles(ROLE_2);
}
// Password encoding
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// Authorized the request based on role
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/resources/**").hasRole(ROLE_1)
.antMatchers("/all").permitAll()
.and().formLogin();
}
}
Related
I have made a custom exception which should give the message to client when raised. However, that does not seem to be working whenever error code is 401 i.e. UNAUTHORIZED. I tried changing the status code to something else, and message showed up.
Note - I have already set the flag server.error.include-message=always in application.properties
BadCredentialsException.java
#ResponseStatus(value = HttpStatus.UNAUTHORIZED)
public class BadCredentialsException extends RuntimeException{
// Runtime exception just needs this, I guess :/
private static final long serialVersionUID = 1;
public BadCredentialsException(String message){
super(message);
}
}
Here's how I have tried raising the exception.
public ResponseEntity<Boolean> loginUser(String username, String password){
// validating username
User user = myUserRepository.findByUsername(username).
orElseThrow(() -> new ResourceNotFoundException("No username: " + username + " found. Please enter a correct username!"));
// validating password
if(!new BCryptPasswordEncoder().matches(password, user.getPassword())){
throw new BadCredentialsException("Incorrect Password. Please enter correct password to login!");
}
return ResponseEntity.ok(true);
}
Note - message is correctly being displayed in the terminal though. Just not showing up on the client.
UPDATE 1 - I have made this particular endpoint to be accessible by everyone, using permitAll(). In postman, when I select select "no auth" and call this endpoint with expected exception, exception does not give the message unless, I give correct basic auth credentials (any role). I have absolutely no clue why this is happening.
UPDATE 2 - Adding SecurityConfiguration code.
SecurityConfiguration.java
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().
authorizeRequests().
antMatchers(HttpMethod.POST, "/api/v2/user/login/**").permitAll().
antMatchers(HttpMethod.POST, "/api/v2/user/", "/api/v2/user", "/api/v2/user/change-role/**").hasAuthority("ROOT").
antMatchers(HttpMethod.GET, "/api/v2/user/", "/api/v2/user").hasAuthority("ROOT").
antMatchers(HttpMethod.POST, "/api/v1/customers/", "/api/v1/customers").hasAnyAuthority("ADMIN", "ROOT").
antMatchers(HttpMethod.GET, "/api/v1/customers/", "/api/v1/customers").hasAnyAuthority("EMPLOYEE", "ADMIN", "ROOT").
anyRequest().
authenticated().
and().
httpBasic();
}
Actually, there are multiple ways to do that.
First, as #SergVasylchak said in the comments, you can use ControllerAdvice.
So the approach is like below:
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(BadCredentialsException.class)
#ResponseBody
#ResponseStatus(value = HttpStatus.UNAUTHORIZED)
private Message handleMessageAuth(BadCredentialsException e, HttpServletRequest request) {
Message message = new Message();
message.setTimestamp(getTimeStamp());
message.setError(HttpStatus.UNAUTHORIZED.getReasonPhrase());
message.setStatus(HttpStatus.UNAUTHORIZED.value());
message.setMessage(e.getMessage());
message.setPath(String.valueOf(request.getRequestURI()));
return message;
}
}
Message is your custom pojo.
public class Message {
private String timestamp;
private int status;
private String error;
private String message;
private String path;
// getters & setters
}
Another solution is that implement AuthenticationEntryPoint.
What is AuthenticationEntryPoint?
It is an interface implemented by ExceptionTranslationFilter,
basically a filter which is the first point of entry for Spring
Security. It is the entry point to check if a user is authenticated
and logs the person in or throws exception (unauthorized). Usually, the
class can be used like that in simple applications but when using
Spring security in REST, JWT etc one will have to extend it to provide
better Spring Security filter chain management.
AuthenticationEntryPoint is used in Spring Web Security to configure
an application to perform certain actions whenever an unauthenticated
client tries to access private resources.
Look at this.
AuthenticationEntryPoint is used to send an HTTP response that
requests credentials from a client. Sometimes a client will
proactively include credentials such as a username/password to request
a resource. In these cases, Spring Security does not need to provide
an HTTP response that requests credentials from the client since they
are already included. In other cases, a client will make an
unauthenticated request to a resource that they are not authorized to
access. In this case, an implementation of AuthenticationEntryPoint is
used to request credentials from the client. The
AuthenticationEntryPoint implementation might perform a redirect to a
log in page, respond with a WWW-Authenticate header, etc.
For example, if you have a JWT authentication the approach is like below.
#Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
// if you want to return 401 status, this is enough.
// if you want to customize your response you can make it as below.
response.setContentType("application/json");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getOutputStream().println("{ \"error\": \"" + authException.getMessage() + "\" }");
}
}
Now it's time to config the AuthenticationEntryPoint in SecurityConfig.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint authenticationEntryPoint;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
.and()
authorizeRequests().
antMatchers(HttpMethod.POST, "/api/v2/user/login/**").permitAll().
antMatchers(HttpMethod.POST, "/api/v2/user/", "/api/v2/user", "/api/v2/user/change-role/**").hasAuthority("ROOT").
antMatchers(HttpMethod.GET, "/api/v2/user/", "/api/v2/user").hasAuthority("ROOT").
antMatchers(HttpMethod.POST, "/api/v1/customers/", "/api/v1/customers").hasAnyAuthority("ADMIN", "ROOT").
antMatchers(HttpMethod.GET, "/api/v1/customers/", "/api/v1/customers").hasAnyAuthority("EMPLOYEE", "ADMIN", "ROOT").
anyRequest().
authenticated().
and().
httpBasic();
}
}
I am working on a Spring Boot protecting my APIs using Spring Security and JWT tokens and I have the following iusse.
I have an API handling endpoint like this: http://localhost:8019/api/admin/user/54/wallet
I have 2 user types:
ADMIN USER: having ADMIN autority defined into the JWT tokend.
CLIENT USER: having CLIENT autority defined into the JWT tokend.
The previous API must be accessible by both the user types (I know that the /admin/ section in the URI is not the best...it will be refactored in the near future).
Then I have this class extending the Spring Boot WebSecurityConfigurerAdapter class and implementing my security configuration:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("customUserDetailsService")
private UserDetailsService userDetailsService;
#Autowired
private JwtConfig jwtConfig;
#Autowired
private JwtTokenUtil jwtTokenUtil;
private static final String[] USER_MATCHER = { "/api/user/email/**"};
private static final String[] CLIENT_MATCHER = {
"/api/users/email/*",
//"/api/admin/**",
"/api/admin/user/{userID}/wallet"
};
private static final String[] ADMIN_MATCHER = {
"/api/users/email/**",
"/api/admin/users",
"/api/admin/user/{userID}/wallet",
"/api/admin/**"
};
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception
{
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
/*
* NOTE:
* Using hasRole expects the authority names to start with 'ROLE_' prefix
* Instead, we use hasAuthority as we can use the names as it is
*/
http.csrf().disable()
.authorizeRequests()
.antMatchers(USER_MATCHER).hasAnyAuthority("USER")
.antMatchers(CLIENT_MATCHER).hasAnyAuthority("CLIENT")
.antMatchers(ADMIN_MATCHER).hasAnyAuthority("ADMIN")
//.antMatchers(CLIENT_MATCHER).hasAnyAuthority("CLIENT")
.antMatchers("/api/users/test").authenticated()
.antMatchers(HttpMethod.POST, jwtConfig.getUri()).permitAll()
.anyRequest().denyAll()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(
new TokenVerificationFilter(authenticationManager(), jwtConfig, jwtTokenUtil),UsernamePasswordAuthenticationFilter.class);
}
/* To allow Pre-flight [OPTIONS] request from browser */
#Override
public void configure(WebSecurity web)
{
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
web.ignoring().antMatchers("/swagger-ui/**",
"/webjars/**",
"/v2/**",
"/swagger-resources/**",
"/swagger-ui.html");
}
#Bean
public BCryptPasswordEncoder passwordEncoder()
{
return new BCryptPasswordEncoder();
};
}
The problem is that as it is defined in the previous configuration if I try to call the previous API (http://localhost:8019/api/admin/user/54/wallet) with a token related to an user having the CLIENT autority I am obtaining this error message:
{
"timestamp": "2022-02-13T20:22:10.418+00:00",
"status": 403,
"error": "Forbidden",
"message": "Forbidden",
"path": "/api/admin/user/54/wallet"
}
Viceversa if I try to call the previous API passing a token of an user having the AMDIN authority I obtain the expected API output.
It seems pretty strange to me because into the CLIENT_MATCHER I have defined this rule:
"/api/admin/user/{userID}/wallet"
The strangest thing is that if I remove this line from the ADMIN_MATCHER:
"/api/admin/user/{userID}/wallet",
that become something like:
private static final String[] ADMIN_MATCHER = {
"/api/users/email/**",
"/api/admin/users",
//"/api/admin/user/{userID}/wallet",
"/api/admin/**"
}
Now the behavior is the complete opposite: using a JWT token of an user having the CLIENT authority I retrieve the expected output, but now using a JWT token of an user having the ADMIN authoruty I obtain this error message:
{
"timestamp": "2022-02-13T20:27:47.793+00:00",
"status": 403,
"error": "Forbidden",
"message": "Forbidden",
"path": "/api/admin/user/54/wallet"
}
and this seems pretty strange to me because the ADMIN_MATCHER contaisn this rule:
"/api/admin/**"
that should meaning: access to all the API having an enptoint starting with "/api/admin/" folloed by anything else.
What is wrong in my Spring configuration? What am I missing? How can I fix it in such a way that this API could be accessible using a JWT token having ADMIN or CLIENT authority?
instead of
.antMatchers(ADMIN_MATCHER).hasAnyAuthority("ADMIN")
try
.antMatchers(ADMIN_MATCHER).hasAnyAuthority("ADMIN", "CLIENT")
and removing this from CLIENT_MATCHER
"/api/admin/user/{userID}/wallet"
The problem is the way you have written your rules. The rule that matches is the one that is used so if a request comes in and it finds "/api/user/email/**" it will find the first that matches, the one for USER.
What you should write is .antMatchers("/api/user/email/**").hasAnyAuthority("USER", "CLIENT", "ADMIN") to allow all of them access. That is your security rule, it will not go over all rules it will detect the one that matches first (which also means that ordering is very important for your rules!).
This applies to all your other rules as well BTW.
I have a react app running on a separate port (localhost:3000) that i want to use to authenticate users with, currently i have a proxy setup to my Spring backend (localhost:8080).
Can I somehow manually authenticate instead of http.httpBasic() by sending a POST request to my backend and getting back a session cookie then include the cookie with every request? It would simplify the auth process on iOS side aswell (using this process i could only store the session cookie value in keychain and pass it with every request made to my api)
How would I disable csrf for non-browser requests?
Is there a better approach to this? Diffrent paths for browser and mobile auth?
{
"username": "user",
"password": "12345678"
}
Handle the request in spring controller
#PostMapping(path = "/web")
public String authenticateUser() {
//Receive the auth data here... and perform auth
//Send back session cookie
return "Success?";
}
My WebSecurityConfigurerAdapter.java
#Configuration
#EnableWebSecurity
public class WebsecurityConfig extends WebSecurityConfigurerAdapter {
private final DetailService detailService;
public WebsecurityConfig(DetailService detailService) {
this.detailService = detailService;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(detailService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().disable()
.authorizeRequests()
.antMatchers(HttpMethod.POST,"/api/v1/authenticate/new").permitAll()
.antMatchers(HttpMethod.POST,"/api/v1/authenticate/web").permitAll()
.anyRequest().authenticated();
}
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods("GET", "POST").allowedOrigins("http://localhost:8080");
}
};
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(14);
}
}
You can create an endpoint that takes user's credentials in a request body, perform authentication and then set tokens, and other required parameters in HttpOnly cookies.
After setting cookies, subsequent requests can read access/refresh token from cookies and add it in requests, you can then use custom CheckTokenEndpoint to verify tokens.
In the following example TokenParametersDto is a POJO that has username and password properties.
For issuing token (by verifying credentials) you can delegate call to TokenEndpoint#postAccessToken(....) or use its logic to your own method.
#PostMapping(path = "/oauth/http/token", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Void> issueToken(#RequestBody final #Valid #NotNull TokenParametersDto tokenParametersDto,
final HttpServletResponse response) {
final OAuth2AccessToken token = tokenService.issueToken(tokenParametersDto);
storeTokenInCookie(token, response);
return new ResponseEntity<>(HttpStatus.OK);
}
private void storeTokenInCookie(final OAuth2AccessToken token, final HttpServletResponse response) {
final Cookie accessToken = new Cookie("access_token", token.getValue());
accessToken.setHttpOnly(true);
accessToken.setSecure(sslEnabled);
accessToken.setPath("/");
accessToken.setMaxAge(cookieExpiration);
final Cookie tokenType = new Cookie("token_type", token.getTokenType());
tokenType.setHttpOnly(true);
tokenType.setSecure(sslEnabled);
tokenType.setPath("/");
tokenType.setMaxAge(cookieExpiration);
// Set Refresh Token and other required cookies.
response.addCookie(accessToken);
response.addCookie(tokenType);
}
Check this answer for disabling CSRF for a specific URL section.
I made some api with REST Spring. GET request works fine in Postman but when I try to do POST request I receive this error :
{
"timestamp": "2018-09-25T06:39:27.226+0000",
"status": 403,
"error": "Forbidden",
"message": "Forbidden",
"path": "/cidashboard/projects"
}
This is my controller :
#RestController
#RequestMapping(ProjectController.PROJECT_URL)
public class ProjectController {
public static final String PROJECT_URL = "/cidashboard/projects";
private final ProjectService projectService;
public ProjectController(ProjectService projectService) {
this.projectService = projectService;
}
#GetMapping
List<Project> getAllProjects(){
return projectService.findAllProjects();
}
#GetMapping("/{id}")
Project getProjectById(#PathVariable int id) {
return projectService.findProjectById(id);
}
#PostMapping
void addProject(#RequestBody Project newProject) {
projectService.saveProject(newProject);
}
}
Security configuration
initial I wanted to work with ldap, but in my application properties i left only the conection at database....................................................................................................................................................
#EnableGlobalMethodSecurity
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/css/**").permitAll();
// .anyRequest().fullyAuthenticated();
// .and()
// .formLogin().loginPage("/login").permitAll()
// .failureUrl("/login-error");
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.userDnPatterns("uid={0},ou=people")
.groupSearchBase("ou=groups")
.contextSource(contextSource())
.passwordCompare()
//.passwordEncoder(new LdapShaPasswordEncoder())
.passwordAttribute("userPassword");
}
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resources/static/**"); // #3
}
#Bean
public DefaultSpringSecurityContextSource contextSource() {
return new DefaultSpringSecurityContextSource(Arrays.asList("ldap://localhost:8389/"), "dc=springframework,dc=org");
}
}
Enable spring security with #EnableWebSecurity usage.By default enables csrf support, you have to disable it to prevent Forbidden errors.
#Override
protected void configure(HttpSecurity http) throws Exception {
http //other configure params.
.csrf().disable();
}
PS: 415 unsupported type --> add to your mapping like this annotation for which type of data is sending from Postman.
#PostMapping(consumes = "application/json")
void addProject(#RequestBody Project newProject) {
projectService.saveProject(newProject);
}
In case you want to solve this issue without compromising security, you can send the xsrf-token with your request in postman.
Create a new environment in Postman (e.g. "local").
Create a new variable in this environment (e.g. "xsrf-token")
Go back to your request and make sure the right environment is selected on the top right corner ("local" in this case)
In your POST request, add a header with key "X-XSRF-TOKEN" and value "{{csrf-token}}"
In the "tests" tab, add following code:
var xsrfCookie = pm.cookies.get('XSRF-TOKEN')
pm.environment.set("xsrf-token", xsrfCookie)
The first time you make this request, you will still get a 403, but you'll also receive a cookie with the xsrf-token. The script will copy this token in the environment variable and the next requests you'll make use the appropriate token.
Check the "User-Agent" included in Headers section, If not add the "User-Agent" field
I I was also getting the same error. I found the solution using a different application, not postman {Insomnia REST Client}.
When I went back to postman after wondering, I realized that it is related to permissions in spring security. So after setting the permissions it will work.
I know, there are many articles about this topic, but I have a problem and I can't find any solution.
I have a classic spring security java config:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuctionAuthenticationProvider auctionAuthenticationProvider;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(auctionAuthenticationProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic();
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequest = http.authorizeRequests();
configureAdminPanelAccess(authorizeRequest);
configureFrontApplicationAccess(authorizeRequest);
configureCommonAccess(authorizeRequest);
http.csrf()
.csrfTokenRepository(csrfTokenRepository()).and()
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class);
http.logout()
.clearAuthentication(true)
.invalidateHttpSession(true);
}
...
}
Also, I have two controller methods, where I login/logout from my web application by AJAX.
When I would like to logout, I first call this method, which I expect to clear user sessions and clear everything from the security context.
#Override
#RequestMapping(value = "/logout", method = GET, produces = APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<Boolean> logout(final HttpServletRequest request, final HttpServletResponse response) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null){
new SecurityContextLogoutHandler().logout(request, response, auth);
}
return new ResponseEntity<>(Boolean.TRUE, HttpStatus.OK);
}
After this I reload my client web application and each time, when it is reloaded, I check whether the user is authenticated by calling the following controller method:
#Override
#RequestMapping(value = "/user", method = GET, produces = APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<UserDetails> user() {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
return new ResponseEntity<>((UserDetails) principal, HttpStatus.OK);
}
return null;
}
And here I aways receive the last authenticated user. It seems that in the previous logout method, Spring logout doesn't work.
Keep in mind that I tried to logout with the following code, without any success:
#Override
#RequestMapping(value = "/logout", method = GET, produces = APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<Boolean> logout(final HttpServletRequest request) {
try {
request.logout();
return new ResponseEntity<>(Boolean.TRUE, HttpStatus.OK);
} catch (ServletException ex) {
if (LOG.isDebugEnabled()) {
LOG.debug("There is a problem with the logout of the user", ex);
}
}
Are you have any idea what I miss in my config and the logout process?
From your question, I see you are trying to create your own logout and you also trying to use the default Spring logout. I advise that you should choose one method and not mix them both. There are two I recommend to logout from Spring:
First: Default spring security logout
.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/logout.done").deleteCookies("JSESSIONID")
.invalidateHttpSession(true)
From the example above, you should only need to call the /logout URL whenever you want to logout the user. No need to create any #Controller to handle that logout instead Spring will help to log the user out. You also can add other thing you want to invalidate here.
Second: Programmatically logout
#RequestMapping(value = {"/logout"}, method = RequestMethod.POST)
public String logoutDo(HttpServletRequest request,HttpServletResponse response){
HttpSession session= request.getSession(false);
SecurityContextHolder.clearContext();
session= request.getSession(false);
if(session != null) {
session.invalidate();
}
for(Cookie cookie : request.getCookies()) {
cookie.setMaxAge(0);
}
return "logout";
}
If you are using this logout approach, you don't need to include the first method in ht eSpring security config. By using this method, you can add an extra action to perform before and after logout done. BTW, to use this logout, just call the /logout url and the user will be logged out manually. This method will invalidate the session, clear Spring security context and cookies.
In addition for the second method, if you are using RequestMethod.POST, you need to include the CSRF key on the POST request. The alternative way is to create a form with a hidden input CSRF key. This is some example of auto generated logout link with jQuery :
$("#Logout").click(function(){
$form=$("<form>").attr({"action":"${pageContext.request.contextPath}"+"/logout","method":"post"})
.append($("<input>").attr({"type":"hidden","name":"${_csrf.parameterName}","value":"${_csrf.token}"}))
$("#Logout").append($form);
$form.submit();
});
You just need to create a hyperlink <a id="Logout">Logout</a> to use it.
If you are using RequestMethod.GET,just include a CSRF key as a parameter in you link like this:
Logout
Thats all, hope it helps.
Just a heads up, there is Clear Site Data HTTP header as shown below
Clear-Site-Data: "cache", "cookies", "storage", "executionContexts"
I also helped add support for Clear-Site-Data header into Spring-Security 5.2 project. For more details around the implementation, see the PR.
Here is a sample of how it is going to work
#EnableWebSecurity
static class HttpLogoutConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.logout()
.addLogoutHandler(new HeaderWriterLogoutHandler(
new ClearSiteDataHeaderWriter(SOURCE)));
}
}
Where SOURCE is a vararg of one or more of the following
"*" Clear everything
One or more of "cache", "cookies", "storage", "executionContexts"
For more details see the sample test in the LogoutConfigurerClearSiteDataTests.java.
This will help, i think clearAuthentication(true) is enough:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
....
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.httpBasic()
.and()
.logout().clearAuthentication(true)
.logoutSuccessUrl("/")
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true)
.and()
I solved my problem similarly by adding the following parameter to the application.properties file
spring.cache.type=NONE
Just change logout URL from "/logout" to "war or snapshot name/logout"