I have created a project with Spring Security and JWT Tokens.
I observed that the path specified in configure method in WebSecurityConfig class does not behave equally for different HTTP methods.
My ConcertRESTController that I am trying to secure looks as following:
#RestController
#RequestMapping("/concerts")
public class ConcertRESTController {
#GetMapping("")
public List<Concert> getAllConcerts() {
// get all concerts logic
}
#PostMapping("")
public ResponseEntity<Concert> addConcert(#RequestBody Concert concert) {
// add concert logic
}
#DeleteMapping("/{id}")
public ResponseEntity<?> deleteConcertById(#PathVariable("id") int id) {
// delete concert logic
}
And WebSecurityConfig:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.authorizeRequests()
.antMatchers("/auth/**").permitAll()
.antMatchers(HttpMethod.GET,"/concerts/**").permitAll()
.antMatchers(HttpMethod.POST, "/concerts/**").hasRole("admin")
.antMatchers(HttpMethod.DELETE, "/concerts/**").hasRole("admin")
.anyRequest().authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore((Filter) authenticationJwtTokenFilter(),
UsernamePasswordAuthenticationFilter.class);
}
However, the only method that works with this pattern is HttpMethod.GET. Other two give me status 403 when trying to call them with admin rights in Postman.
"status": 403,
"error": "Forbidden",
"message": "Forbidden",
"path": "/concerts"
What is interesting, when I change them to:
.antMatchers(HttpMethod.GET,"/concerts/**").permitAll()
.antMatchers(HttpMethod.POST, "concerts/**").hasRole("admin")
.antMatchers(HttpMethod.DELETE, "concerts/**").hasRole("admin")
All works as expected.
Could anyone explain this behaviour? Thank you in advance!
----------------------------------------------------------------------
Edit - adding code for JwtAuthTokenFilter class
public class JwtAuthTokenFilter extends OncePerRequestFilter {
#Autowired
private JwtProvider tokenProvider;
#Autowired
private UserDetailsServiceImpl userDetailsService;
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
FilterChain filterChain) throws ServletException, IOException {
try {
String jwt = getJwt(httpServletRequest);
if (jwt != null && tokenProvider.validateJwtToken(jwt)) {
String username = tokenProvider.getUserNameFromJwtToken(jwt);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
logger.error("Can NOT set user authentication -> Message: {}", e);
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
private String getJwt(HttpServletRequest request) {
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
return authHeader.replace("Bearer ", "");
}
return null;
}
}
In WebSecurityConfig:
#Bean
public JwtAuthTokenFilter authenticationJwtTokenFilter() {
return new JwtAuthTokenFilter();
}
I think there is no issue with your end points. The problem is with your role.
hasRole automatically inserts "ROLE_" in the parameters.
You need to make sure few things:
1) If your role is stored as "ROLE_admin", then use hasRole("admin").
2) If your role is stored as "admin", then you should use hasAuthority("admin").
What is interesting, when I change them to:
.antMatchers(HttpMethod.GET,"/concerts/**").permitAll()
.antMatchers(HttpMethod.POST, "concerts/**").hasRole("admin")
.antMatchers(HttpMethod.DELETE, "concerts/**").hasRole("admin")
In above case FilterSecurityInterceptor does not consider your end point as Secure object instead it consider it as Public object, so make sure your pattern starts with forward slash. You can see below logs:
2020-06-06 17:22:56 DEBUG AntPathRequestMatcher:176 - Checking match of request : '/concerts'; against 'concerts/**'
2020-06-06 17:22:56 DEBUG FilterSecurityInterceptor:210 - Public object - authentication not attempted
2020-06-06 17:44:23 DEBUG AntPathRequestMatcher:176 - Checking match of request : '/concerts'; against '/concerts/**'
2020-06-06 17:44:23 DEBUG FilterSecurityInterceptor:219 - Secure object: FilterInvocation: URL: /concerts; Attributes: [hasRole('ROLE_Admin')]
Related
I have this response when it comes to check the user Unauthorized.
i there any possibility to remove the Path from the Unauthorized response ? since it does not gives valuable information for the user
{
"timestamp": "2021-03-18T09:16:09.699+0000",
"status": 401,
"error": "Unauthorized",
"message": "Unauthorized",
"path": "/test/v1/api/test.com/config/settings"
}
this is how my config looks like
public class ResourceConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.cors();
httpSecurity
.anonymous().disable()
.requestMatchers().antMatchers("/api/**")
.and()
.authorizeRequests()
.antMatchers("/api/**")
.authenticated()
.and()
.exceptionHandling()
.accessDeniedHandler(new OAuth2AccessDeniedHandler());
}
Adding on #linhx idea of using custom AuthenricationEntryPoint, you can use HandlerExceptionResolver which resolves to a page.
You can get a detailed comparison of different approaches here.
#Component
public class ABAuthenticationEntryPoint implements AuthenticationEntryPoint {
protected final Logger logger = LoggerFactory.getLogger(ABAuthenticationEntryPoint.class);
private final String realmName = "CustomRealm";
#Autowired
#Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver resolver;
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
resolver.resolveException(request, response, null, authException);
}
}
The HandlerExceptionResolver uses the handler (HandlerMethod) to obtain the Controller class and scan it for methods annotated with #ExceptionHandler. If one of this methods matches the exception (ex) then this methods get invoked in order to handle the exception. (else null get returned signaling that this exception resolver feels no responsible).
So, add a class with #ControllerAdvice:
#ExceptionHandler(value = InsufficientAuthenticationException.class)
public ResponseEntity<Object> handleInsufficientAuthenticationException(InsufficientAuthenticationException ex) {
String methodName = "handleInsufficientAuthenticationException()";
return buildResponseEntity(HttpStatus.UNAUTHORIZED, null, null, ex.getMessage(), null);
}
private ResponseEntity<Object> buildResponseEntity(HttpStatus status, HttpHeaders headers, Integer internalCode, String message, List<Object> errors) {
ResponseBase response = new ResponseBase()
.success(false)
.message(message)
.resultCode(internalCode != null ? internalCode : status.value())
.errors(errors != null
? errors.stream().filter(Objects::nonNull).map(Objects::toString).collect(Collectors.toList())
: null);
return new ResponseEntity<>((Object) response, headers, status);
}
SecurityConfig class:
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
protected final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
#Autowired
private ABAuthenticationEntryPoint authenticationEntryPoint;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.
.....
.and()
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint); //AuthenticationEntryPoint has to be the last
}
}
Finally you will get something like the following, based on how you buildResponseEntity
{
"success": false,
"resultCode": 401,
"message": "Full authentication is required to access this resource"
}
I have spent days on trying to get ROLES working within Spring Security using JWT tokens. Authentication seems to work fine but authorization seems to still not be working.
My current setup is that I have...
an In Memory Database for Users (I have one single user called "lion", and one single role "KING")
an authenticate end point which authenticates the user, and returns the JWT in the response.
My WebCofig looks like this :
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/hello").permitAll()
.antMatchers("/authenticate").permitAll()
.anyRequest().authenticated()
.and().exceptionHandling()
.and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
I Basically want to say
anyone can access (/)
anyone can access "hello" and "authenticate"
My web controller looks like this :
#Controller
public class WebController {
#PreAuthorize("permitAll()")
#RequestMapping("/hello")
#ResponseBody
public String hello() {
return "Hello! ALL USERS logged in or not are allowed to see this!";
}
#PreAuthorize("hasRole('KING')")
#RequestMapping("/king")
#ResponseBody
public String king() {
return "Hello King";
}
#PreAuthorize("hasRole('ROLE_KING')")
#RequestMapping("/king2")
#ResponseBody
public String king2() {
return "Hello King2";
}
}
My web token returned via my authentication looks good (i think..). Decoded the body looks like this :
{
"sub": "lion",
"scopes": "KING",
"iat": 1580645802,
"exp": 1580663802
}
... and will allow me to access anywhere where I just need to be authenticated. "anyRequest().authenticated()".
However both king and king2 REST endpoints give me 403 - "Access Denied" even though my user has role KING (I tried both KING and ROLE_KING)
I feel like I am missing a piece of the puzzle.
My JWT Filter seems to look good too. The authentication object (..which is set in the security context handler like so : SecurityContextHolder.getContext().setAuthentication(authentication);) has exactly ONE SimpleGrantedAuthority with role = KING.
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
username = jwtUtil.extractUsername(jwt);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, userDetails)) {
userDetails.getAuthorities(); //has exactly ONE SimpleGrantedAuthority with value "KING"
SimpleGrantedAuthority s = new SimpleGrantedAuthority("asfd");
s.getAuthority(); //string
UsernamePasswordAuthenticationToken authentication = jwtUtil.getAuthentication(jwt,
SecurityContextHolder.getContext().getAuthentication(), userDetails);
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
logger.info("authenticated user " + username + ", setting security context");
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(request, response);
}
My UserDetailsService looks like this :
public class UserService implements UserDetailsService {
#Autowired
private UserRepository repository;
#Autowired
private PasswordValidationService passwordValidationService;
#Override
public User loadUserByUsername(String userName) throws UsernameNotFoundException {
UserEntity u = repository.findByUserNameIgnoreCase(userName);
SimpleGrantedAuthority a = new SimpleGrantedAuthority(u.getRole().toString());
ArrayList<GrantedAuthority> auths = new ArrayList<GrantedAuthority>();
auths.add(a);
return new User(u.getUserName(), u.getPassword(), auths);
}
}
Everything looks good to me which is why I am confused right now :(
*********** UPDATE ************
If I use hasAuthority instead of hasRole then this works :
#PreAuthorize("hasAuthority('KING')")
#RequestMapping("/king3")
#ResponseBody
public String king3() {
return "Hello King";
}
So how do I get hasRole to work? Am I missing the appending of "ROLE_" somewhere in my code?
I am trying to toggle/bypass/disable Spring Security (Authentication and Authorization) for all the requests having particular Request Header.
For example, if a request url is hit with that Request Header, Spring Security should be bypassed, if not it should not be bypassed.
For this, I am using following requestMatchers Spring Security config:
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.GET)
.antMatchers(HttpMethod.OPTIONS)
.requestMatchers(new RequestHeaderRequestMatcher("TEST-HEADER","TEST-VALUE"));
}
My remaining Security Config is :
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity (prePostEnabled = true)
#ConditionalOnProperty (name = "security.enabled", havingValue = "true", matchIfMissing = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private SecurityProps securityProps;
#Autowired
private MyUserDetailsService myUserDetailsService;
#Autowired
private MyAuthenticationEntryPoint myAuthenticationEntryPoint;
#Autowired
private MyCORSFilter myCORSFilter;
public SecurityConfig() {
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.addFilterBefore(myCORSFilter, SessionManagementFilter.class)
.addFilterBefore(requestHeaderFilter(), RequestHeaderAuthenticationFilter.class)
.authenticationProvider(preauthAuthProvider())
.authorizeRequests()
.antMatchers(HttpMethod.GET, securityProps.getNoAuthGetPattern()).permitAll()
.antMatchers(HttpMethod.OPTIONS, securityProps.getNoAuthOptionsPattern()).permitAll()
.requestMatchers(new RequestHeaderRequestMatcher("TEST-HEADER","TEST-VALUE")).permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(myAuthenticationEntryPoint);
}
#Autowired
#Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(preauthAuthProvider());
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.GET)
.antMatchers(HttpMethod.OPTIONS)
.requestMatchers(new RequestHeaderRequestMatcher("TEST-HEADER","TEST-VALUE"));
}
public RequestHeaderAuthenticationFilter requestHeaderFilter() throws Exception {
RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter = new RequestHeaderAuthenticationFilter();
requestHeaderAuthenticationFilter.setPrincipalRequestHeader(MySecurityConstants.LOGIN_HEADER);
requestHeaderAuthenticationFilter.setAuthenticationManager(authenticationManager());
requestHeaderAuthenticationFilter.setExceptionIfHeaderMissing(false);
requestHeaderAuthenticationFilter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
if (exception instanceof MySecurityException) {
myAuthenticationEntryPoint.commenceMySecurityException(request, response, (MySecurityException) exception);
} else if (exception instanceof UsernameNotFoundException) {
myAuthenticationEntryPoint.commenceUsernameNotFoundException(request, response,
(UsernameNotFoundException) exception);
} else if (exception instanceof PreAuthenticatedCredentialsNotFoundException) {
myAuthenticationEntryPoint.commence(request, response, exception);
}
}
});
return requestHeaderAuthenticationFilter;
}
#Bean
public PreAuthenticatedAuthenticationProvider preauthAuthProvider() throws Exception {
PreAuthenticatedAuthenticationProvider authProvider = new PreAuthenticatedAuthenticationProvider();
authProvider.setPreAuthenticatedUserDetailsService(userDetailsServiceWrapper());
return authProvider;
}
#Bean
public UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> userDetailsServiceWrapper()
throws Exception {
UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> wrapper =
new UserDetailsByNameServiceWrapper<>();
wrapper.setUserDetailsService(ivyUserDetailsService);
return wrapper;
}
}
With the above settings, I am unable to disable/bypass Spring Security and I am getting the AuthenticationCredentialsNotFoundException exception:
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
Can anyone help me by identifying what am I doing wrong? Is my approach correct or I need to do something else to achieve this?
EDIT :
I am getting this exception in org.springframework.security.access.intercept.AbstractSecurityInterceptor class in beforeInvocation() method where it tries to get the authentication object from SecurityContextHolder. AbstractSecurityInterceptor is invoked by its subclass MethodSecurityInterceptor which is invoked from my Spring Controller which is annotated with #PreAuthorize.
I think your bypass is working fine. Its skipping the check.
The security's authorization check part gets the authenticated object from SecurityContext, which will be set when a request gets through the spring security filter.
So when you skip security filter SecurityContext is not set yet thus the error
You can do something like this to set it manually for your Custom Header Case
try {
SecurityContext ctx = SecurityContextHolder.createEmptyContext();
SecurityContextHolder.setContext(ctx);
ctx.setAuthentication(event.getAuthentication());
} finally {
SecurityContextHolder.clearContext();
}
Edit 1:
Answering all the queries.
But if thats the case, then I guess all GET call should also have
failed, but my GET calls are working fine.
Since you have added this line All your GET calls are skipped from security check.
.antMatchers(HttpMethod.GET, securityProps.getNoAuthGetPattern()).permitAll()
where can I add the code you have mentioned? Any particular filter or
somewhere else ?
I have done something like this in a Filter.
Refer Here
Look at TokenAuthenticationFilter Class in Answer. Where am manually setting.
Note: Its JWT implementation but good to refer
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (tokenHelper.validateToken(authToken, userDetails)) {
// create authentication
TokenBasedAuthentication authentication = new TokenBasedAuthentication(userDetails);
authentication.setToken(authToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
What is event in your answer?
I just got that case from Some Answer, cant find its link now. But you can setAuthentication like this or like above
Authentication authentication = new PreAuthenticatedAuthenticationToken("system", null);
authentication.setAuthenticated(true);
context.setAuthentication(authentication);
I got a simple problem for you guys. I face the problem that I always get a 404 response if I implement Spring Security in my Spring Boot application.
I debugged through the entire code and I just find out that the redirect path is always "/" and not the called URL.
For example
call localhost:8080/user/login/
-> Spring Security check given credentials
-> they are correct
-> resolve given path
-> SOMETHING STANGES HAPPENS HERE (Refer to figure (1))
-> resolve path to "/" and not "/user/login/"
-> Therefore I get the response 404 NOT FOUND because it returns the wrong path
Debugged mode -- determineTargetUrl -> this.targetUrlParameter is not set and therefore the targetUrl will be "/" and not the real targetUrl "/user/login/" . The code is shown in figure (1).
figure(1) Spring Secutiry class - AbstractAuthenticationTargetUrlRequestHandler
My Code with Spring Security
WebSecurityConfig
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.disable()
.csrf()
.disable()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/user/login/").permitAll()
.antMatchers(HttpMethod.GET, "/user/secret/**").permitAll()
.and()
.addFilterBefore(new JWTLoginFilter("/user/login/", authenticationManager()), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
JWTLoginFilter
public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter {
public JWTLoginFilter(String url, AuthenticationManager authManager) {
super(new AntPathRequestMatcher(url));
setAuthenticationManager(authManager);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException, IOException, ServletException {
User user = new ObjectMapper().readValue(req.getInputStream(), User.class);
Optional<User> dbUser = SpringContextBridge.services().getUserRepository().findUserByEmail(user.getEmail());
dbUser.ifPresent(us -> user.setPassword(SecretGenerator.generateSha256(user.getPassword(), us.getSecret())));
return getAuthenticationManager()
.authenticate(new UsernamePasswordAuthenticationToken(user.getEmail(), user.getPassword(), Collections.emptyList())
);
}
}
JWTAuthenticationFilter
public class JWTAuthenticationFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
Authentication authentication = TokenAuthenticationService.getAuthentication((HttpServletRequest) request);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
}
}
UserDetailsServiceImpl
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
private UserRepository userRepository;
#Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
Optional<User> user = userRepository.findUserByEmail(email);
if (!user.isPresent()) {
throw new UserNotFoundException("User with email: " + email + " not found");
}
return new org.springframework.security.core.userdetails.User(user.get().getEmail(), user.get().getPassword(), Collections.emptyList());
}
}
Maybe someone can help me!
I had a similar problem and the solution that worked for me was to override the successfulAuthentication method of AbstractAuthenticationProcessingFilter like this
override fun successfulAuthentication(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain, authResult: Authentication?) {
logger.info("Successful Authentication for user ${authResult?.principal}")
SecurityContextHolder.getContext().authentication = authResult
chain.doFilter(request, response)
}
Note: the example is in kotlin
I have the simplest oauth2 client:
#EnableAutoConfiguration
#Configuration
#EnableOAuth2Sso
#RestController
public class ClientApplication {
#RequestMapping("/")
public String home(Principal user, HttpServletRequest request, HttpServletResponse response) throws ServletException {
return "Hello " + user.getName();
}
public static void main(String[] args) {
new SpringApplicationBuilder(ClientApplication.class)
.properties("spring.config.name=application").run(args);
}
}
I also have the following application.yml:
server:
port: 9999
servlet:
context-path: /client
security:
oauth2:
client:
client-id: acme
client-secret: acmesecret
access-token-uri: http://localhost:8080/oauth/token
user-authorization-uri: http://localhost:8080/oauth/authorize
resource:
user-info-uri: http://localhost:8080/me
logging:
level:
org.springframework.security: DEBUG
org.springframework.web: DEBUG
It is the full code. I don't have any additional source code. It works properly.
But now I want to add a logout feature. I've added an endpoint but it doesn't work. I tried to do the following:
#RequestMapping("/logout")
public void logout(HttpServletRequest request, HttpServletResponse response) throws ServletException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
authentication.setAuthenticated(false);
new SecurityContextLogoutHandler().logout(request,response,authentication);
SecurityContextHolder.clearContext();
request.logout();
request.getSession().invalidate();
}
But I am still logged in and can access / url and it responds to me with the username.
Can you help me fix this issue?
Update
I tried the approach described here https://spring.io/guides/tutorials/spring-boot-oauth2/#_social_login_logout :
#EnableAutoConfiguration
#Configuration
#EnableOAuth2Sso
#Controller
public class ClientApplication extends WebSecurityConfigurerAdapter {
private Logger logger = LoggerFactory.getLogger(ClientApplication.class);
#RequestMapping("/hello")
public String home(Principal user, HttpServletRequest request, HttpServletResponse response, Model model) throws ServletException {
model.addAttribute("name", user.getName());
return "hello";
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http.antMatcher("/**")
.authorizeRequests()
.antMatchers( "/login**", "/webjars/**", "/error**").permitAll()
.anyRequest()
.authenticated()
.and().logout().logoutSuccessUrl("/").permitAll()
.and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
// #formatter:on
}
public static void main(String[] args) {
new SpringApplicationBuilder(ClientApplication.class)
.properties("spring.config.name=application").run(args);
}
}
and on FE I wrote:
<script type="text/javascript">
$.ajaxSetup({
beforeSend: function (xhr, settings) {
if (settings.type == 'POST' || settings.type == 'PUT'
|| settings.type == 'DELETE') {
if (!(/^http:.*/.test(settings.url) || /^https:.*/
.test(settings.url))) {
// Only send the token to relative URLs i.e. locally.
xhr.setRequestHeader("X-XSRF-TOKEN",
Cookies.get('XSRF-TOKEN'));
}
}
}
});
var logout = function () {
$.post("/client/logout", function () {
$("#user").html('');
$(".unauthenticated").show();
$(".authenticated").hide();
});
return true;
};
$(function() {
$("#logoutButton").on("click", function () {
logout();
});
});
</script>
and
<input type="button" id="logoutButton" value="Logout"/>
But it still doesn't work. It results in the following behavior:
Post http://localhost:9999/client/logout redirects to the http://localhost:9999/client but this page doesn't exist
source code on gitub:
client - https://github.com/gredwhite/logour_social-auth-client (use localhost:9999/client/hello url)
server - https://github.com/gredwhite/logout_social-auth-server
You can delete the refresh token as well as access token from database to save space.
#PostMapping("/oauth/logout")
public ResponseEntity<String> revoke(HttpServletRequest request) {
try {
String authorization = request.getHeader("Authorization");
if (authorization != null && authorization.contains("Bearer")) {
String tokenValue = authorization.replace("Bearer", "").trim();
OAuth2AccessToken accessToken = tokenStore.readAccessToken(tokenValue);
tokenStore.removeAccessToken(accessToken);
//OAuth2RefreshToken refreshToken = tokenStore.readRefreshToken(tokenValue);
OAuth2RefreshToken refreshToken = accessToken.getRefreshToken();
tokenStore.removeRefreshToken(refreshToken);
}
} catch (Exception e) {
return ResponseEntity.badRequest().body("Invalid access token");
}
return ResponseEntity.ok().body("Access token invalidated successfully");
}
The URL to logout will be : http://localhost:9999/oauth/logout
Also, pass the access token in the Authorization header, as
Authorization: Bearer 0cb72897-c4f7-4f01-aed9-2f3f79a75484
where, 0cb72897-c4f7-4f01-aed9-2f3f79a75484 is the access token.
Since, its Spring security, don't forget to bypass /oauth/logout url from authorize access, as
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/hello", "/oauth/logout");
}
Hope, it will solve your logout problem in Springboot2+Oauth2. Its working for me.
Add following code snippet to your ClientApplication class. This will also clear your session details.
Replace below code with the configure method of your web security adapter class.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers( "/login**", "/webjars/**", "/error**").permitAll()
.anyRequest()
.authenticated()
.and().logout().invalidateHttpSession(true)
.clearAuthentication(true).logoutSuccessUrl("/login?logout").deleteCookies("JSESSIONID").permitAll().and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
You probably want to use the Spring Security built-in support for the /logout endpoint which will do the right thing (clear the session and invalidate the cookie). To configure the endpoint extend the existing configure() method in our WebSecurityConfigurer:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.and().logout().logoutSuccessUrl("/").permitAll();
}
You can change Post to
Get http://localhost:9999/client/logout
it works for me
Try to add logout url to your security configuration.
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/")
.permitAll();