I am wondering if there is a way to provide two separate types of authentication?
User should log, register, get user data for endpoints /login, /register, /user using basic auth. And when I call /api it should only be authenticated with JWT token provided in headers.
But when I call /api I get all data without any authentication. When user is logged and call /user, API gives JWT to access /api.
My code:
Configuration for basic auth:
#Configuration
#EnableWebSecurity
#Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf().disable();
http
.authorizeRequests()
.antMatchers("/user").authenticated()
.antMatchers("/register").permitAll()
.and()
.formLogin().permitAll()
.defaultSuccessUrl("/user");
}
Configuration for JWT auth:
#Configuration
#Order(2)
public class JWTSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.antMatcher("/api/**")
.addFilterAfter(new JWTAuthorizationFilter(),UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic().disable();
}
I had the same problem, I wanted basic Authentication for some endpoints and for some other I wanted other authentication methods. like yours. you wanna basic authentication for some of the endpoints (/login,/register, /user ) and JWT authentication for some other(/api/**).
I used some tutorials about multiple entry points in spring security but it didn't work.
So here is my solution (It worked)
Separate basic authentication from JWT authentication by creating a custom filter.
Add a prefix path for the endpoints that should be authenticated using basic authentication. like :
(/basic/login, /basic/register,/basic/user)
Create a new custom filter for /basic prefix (for /basic requests) and check basic authentication
#Component
public class BasicAuthenticationFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
//Check for the requests that starts with /basic
if (httpServletRequest.getRequestURI().startsWith("/basic/")) {
try {
//Fetch Credential from authorization header
String authorization = httpServletRequest.getHeader("Authorization");
String base64Credentials = authorization.substring("Basic".length()).trim();
byte[] credDecoded = Base64.getDecoder().decode(base64Credentials);
String credentials = new String(credDecoded, StandardCharsets.UTF_8);
final String username = credentials.split(":", 2)[0];
final String password = credentials.split(":", 2)[1];
//Check the username and password
if (username.equals("admin") && password.equals("admin")) {
//continue
chain.doFilter(request, response);
} else
throw new AuthenticationCredentialsNotFoundException("");
} catch (Exception e) {
throw new AuthenticationCredentialsNotFoundException("");
}
} else chain.doFilter(request, response);
}
}
Write main security configuration just for JWT and permit /basic URL
#Configuration
#EnableWebSecurity
public class JWTSecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/basic/**").permitAll().and()
.csrf().disable()
.antMatcher("/api/**")
.addFilterAfter(new JWTAuthorizationFilter(),UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic().disable();
}
Related
I have SpringSecurityWebAppConfig class that uses my own filter class JwtAuthenticationFilter.
The question now is how do i bypass my JwtAuthenticationFilter for api calls that does not have request header and/or token. Do I set it as a configurable and read it in the filter?
My JwtAuthenticationFilter is an imported class from another library. The purpose is to reuse the file for the other microservices.
Example:
/scanFile does not need request token. When I add into SpringSecurityWebAppConfig. My filter is throw 401 as it does not have request token.
SpringSecurityWebAppConfig class:
public class SpringSecurityWebAppConfig extends WebSecurityConfigurerAdapter {
#Autowired
public JwtAuthenticationFilter jwtAuthenticationFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/homePage").access("hasRole('ROLE_USER') or hasRole('ROLE_ADMIN')")
.antMatchers("/userPage").access("hasRole('ROLE_USER')")
.antMatchers("/adminPage").access("hasRole('ROLE_ADMIN')")
.antMatchers(HttpMethod.DELETE, "/data").access("hasRole('ROLE_ADMIN')")
.antMatchers("/login").permitAll()
.antMatchers("/logout").authenticated()
.and()
.anonymous().disable()
.exceptionHandling()
.authenticationEntryPoint(new CustomAuthenticationEntryPoint())
.and()
.headers()
.httpStrictTransportSecurity()
.includeSubDomains(true).maxAgeInSeconds(31536000);
// Add a filter to validate the tokens with every request
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}
JwtAuthenticationFilter Class:
private void getJwtFromRequest(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String bearerToken = request.getHeader("Authorization");
// Step 1: Check if bearer token exist in authorization header and if bearer token start with "Bearer "
if (!StringUtils.hasText(bearerToken) || !bearerToken.startsWith("Bearer ")) {
String errorMsg = "No access token found in request headers.";
Error err = new Error(JWT_AUTHENTICATION_FILTER, "AccessTokenMissingException", errorMsg);
// Java object to JSON string
String jsonString = mapper.writeValueAsString(err);
log.error(jsonString);
throw new AccessTokenMissingException(errorMsg);
}
//rest of the processing here ...
}
so you want to whitelist some apis even without spring security authentication?
you can use web.ignoring().antMatchers() in your SpringSecurityWebAppConfig, to exclude the urls from spring security.
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/scanFile/**”);
}
I have setup a Spring boot application running in tomcat to be distributed as WAR to server
I have several HTML pages that does work with form security but I am trying to add a API also therefore I am switching to JWT.
My effort is then to combine local client with back end API in one WAR file as I happen to know it is possible with Spring Security
EDIT: My gradle
plugins {
id 'org.springframework.boot' version '2.2.1.RELEASE'
id 'io.spring.dependency-management' version '1.0.8.RELEASE'
id 'java'
id 'war'
}
....
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'com.auth0:java-jwt:3.8.3'
in my SecurityConfiguration.java I have
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// remove csrf and state in session because in jwt we do not need them
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// add jwt filters (1. authentication, 2. authorization)
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.addFilter(new JwtAuthorizationFilter(authenticationManager(), this.userRepository))
.authorizeRequests()
// configure access rules
.antMatchers("/index.html").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers(HttpMethod.POST, "/login").permitAll()
.antMatchers("/api/public/management/*").hasRole("MANAGER")
.antMatchers("/api/public/admin/*").hasRole("ADMIN")
.anyRequest().authenticated();
}
EDIT: Added the JwtAuthenticationFilter
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
/* Trigger when we issue POST request to /login
We also need to pass in {"username":"admin", "password":"password"} in the request body
*/
#Override
public Authentication attemptAuthentication(
HttpServletRequest request,
HttpServletResponse response
) throws AuthenticationException {
// Grab credentials and map them to LoginViewModel
LoginViewModel credentials = null;
try {
credentials = new ObjectMapper().readValue(request.getInputStream(), LoginViewModel.class);
} catch (IOException e) {
e.printStackTrace();
}
// Create login token
assert credentials != null;
System.out.println("Credentials : " + credentials.getUsername() + ":" + credentials.getPassword());
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
credentials.getUsername(),
credentials.getPassword(),
new ArrayList<>()
);
// Authenticate user
return authenticationManager.authenticate(authenticationToken);
}
#Override
protected void successfulAuthentication(
HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult
) {
// Grab principal
UserPrincipal principal = (UserPrincipal) authResult.getPrincipal();
// Create JWT Token
String token = JWT.create()
.withSubject(principal.getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.sign(HMAC512(SECRET.getBytes()));
System.out.println("Token : " + TOKEN_PREFIX + token);
// Add token in response
response.addHeader(HEADER_STRING, TOKEN_PREFIX + token);
}
}
EDIT: Added JwtAuthorizationFilter
public class JwtAuthorizationFilter extends BasicAuthenticationFilter {
private UserRepository userRepository;
JwtAuthorizationFilter(AuthenticationManager authenticationManager, UserRepository userRepository) {
super(authenticationManager);
this.userRepository = userRepository;
}
#Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain chain
) throws IOException, ServletException {
// Read the Authorization header, where the JWT token should be
String header = request.getHeader(HEADER_STRING);
// If header does not contain BEARER or is null delegate to Spring impl and exit
if (header == null || !header.startsWith(TOKEN_PREFIX)) {
chain.doFilter(request, response);
return;
}
// If header is present, try grab user principal from database and perform authorization
Authentication authentication = getUsernamePasswordAuthentication(request);
SecurityContextHolder.getContext().setAuthentication(authentication);
// Continue filter execution
chain.doFilter(request, response);
}
private Authentication getUsernamePasswordAuthentication(HttpServletRequest request) {
String token = request.getHeader(HEADER_STRING)
.replace(TOKEN_PREFIX, "");
// parse the token and validate it
String userName = JWT.require(HMAC512(SECRET.getBytes()))
.build()
.verify(token)
.getSubject();
// Search in the DB if we find the user by token subject (username)
// If so, then grab user details and create spring auth token using username, pass, authorities/roles
if (userName != null) {
System.out.println("userName :" + userName);
User user = userRepository.findByUsername(userName);
UserPrincipal principal = new UserPrincipal(user);
return new UsernamePasswordAuthenticationToken(userName, null, principal.getAuthorities());
}
return null;
}
Normally I could just execute the index.html from the browser by running https://localhost:8443/index
It does give me an error
There was an unexpected error (type=Forbidden, status=403). Access
Denied
HOWEVER if i test it in Postman WITH a Bearer token It serves the page
My question is how to exclude the HTML content from the JwtAuthenticationFilter and how to actually do I authorize my HTML content?
My thoughts is to combine formLogin() with JWT but I cannot find sample code to teach me.
Please ask any other code part if you need
It's hard to say exactly what's going on with so much custom code, but here are some tips based on your explanation.
Possibility #1
First, your JwtAuthenticationFilter or your JwtAuthorizationFilter may be expecting too much. Do they try and authentication and authorize the request when there is no bearer token in the request?
A good way to simplify this is to upgrade to Spring Security 5.1+ and use its bearer token support:
http
.authorizeRequests()
.antMatchers("/index.html").permitAll()
... // other matchers
.anyRequest().authenticated()
.and()
.oauth2ResourceServer()
.jwt();
It only activates when there is a bearer token present. Also, it disables CSRF for you for requests that contain a bearer token.
You can add formLogin(), but it is a bit at odds with sessionCreationPolicy(STATELESS). Form login needs a sessioned web application. If you want a username/password client that sends its credentials on every request, then you need httpBasic() instead, which you can do like so:
http
.authorizeRequests()
.antMatchers("/index.html").permitAll()
... // other matchers
.anyRequest().authenticated()
.and()
.httpBasic()
.and()
.oauth2ResourceServer()
.jwt();
With this setup, httpBasic will engage only when using Authorization: Basic and oauth2ResourceServer will engage only when using Authorization: Bearer.
If you really are wanting formLogin (a UI where a user can fill out an HTML form), then you don't want the application to be stateless. You can do:
http
.authorizeRequests()
.antMatchers("/index.html").permitAll()
... // other matchers
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.oauth2ResourceServer()
.jwt();
Possibility #2
Or, second, you might say that the URIs for your API are completely separate from your client application.
For example, URIs for your client app all start with /app.
And URIs for your API all start with /api.
If that's the case, then you can create two instances of WebSecurityConfigurerAdapter:
#Configuration
#Order(100)
public class AppConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) {
http
.requestMatchers()
.antMatchers("/app/**")
.and()
.authorizeRequests()
.antMatchers("/app/index.html").permitAll()
.anyRequest().authenticated()
.and()
.formLogin();
}
}
#Configuration
#Order(101)
public class ApiConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) {
http
.requestMatchers()
.antMatchers("/api/**")
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(STATELESS)
.and()
.addFilter(new JwtAuthenticationFilter(...))
.addFilter(new JwtAuthorizationFilter(...))
.csrf().disable();
}
}
Note that both of these setups acknowledge that you don't want to disable CSRF for your entire application. You want that protection on for the client app.
After investigating I found the answer
Thank you #jzheaux for your Input it was instrumental in clarifying what I need to do.
I needed to split the API from the WebClient and have JWT on the Api part but FormLogin on the WebClient part.
It is is probably possible to to combine the two but my experience with coding is not there yet
I am having a hard time configuring my spring security. The problem is, my authentication filter always skips my success and failure handlers whenever I authenticate via a custom UsernamePasswordAuthenticationFilter. I don't seem to know why this happens.
First off, I pass the authentication parameter as JSON, and filter out the username and password, then I pass those two parameters into a new UsernamePasswordAuthenticationToken(username, password) then I get the authentication manager and authenticate the returned token. At the point of success full authentication I expect that the success handler should take over but no it doesn't get called at all.
This is my security configuration.
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests()
.and()
.exceptionHandling()
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and()
.authorizeRequests()
.antMatchers("/signup")
.permitAll()
.antMatchers("/", "/security/login", "/request", "/request.html")
.authenticated()
.and()
.formLogin()
.loginProcessingUrl("/security/login")
.successHandler(authenticationSuccessHandler())
.failureHandler(authenticationFailureHandler())
.and()
.logout()
.logoutUrl("/logout")
.permitAll()
.and()
.addFilterAfter
(authenticationFilter(), UsernamePasswordAuthenticationFilter.class)
//.and()
.userDetailsService(userDetailsServiceBean());
}
The relevant beans are
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsServiceBean());
}
#Bean
#Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return new JdbcUserDetails();
}
#Bean
public RestAuthenticationSuccessHandler authenticationSuccessHandler(){
return new RestAuthenticationSuccessHandler();
}
#Bean
public RestAuthenticationFailureHandler authenticationFailureHandler(){
return new RestAuthenticationFailureHandler();
}
#Bean
JsonAuthenticationFilter authenticationFilter() throws Exception {
logger.debug("Authenication filter processing loggin request ");
JsonAuthenticationFilter filter = new JsonAuthenticationFilter();
filter.setAuthenticationManager(authenticationManagerBean());
return filter;
}
The filter is
public class JsonAuthenticationFilter extends UsernamePasswordAuthenticationFilter{
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
UsernamePasswordAuthenticationToken authRequest = this.getUserNamePasswordAuthenticationToken(request);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
and finally my success handler
class RestAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication)
throws ServletException, IOException {
logger.debug("Successful login");
System.out.println("\n\n\n\n\n\n\n\nresponse here\n\n\n\n\n\n\n");
response.getWriter().write("{This is a login success response}");
response.getWriter().flush();
response.getWriter().close();
}
I have been battling for too long
Spring Security will back off on a given bean configuration when you supply that bean.
So, because you supplied your filter (JsonAuthenticationFilter), Spring Security expects that you'll know best how to compose it.
So, then, you'd instead do:
#Bean
JsonAuthenticationFilter authenticationFilter() {
JsonAuthenticationFilter filter = new JsonAuthenticationFilter();
// .. other configs
filter.setAuthenticationSuccessHandler(new RestAuthenticationSuccessHandler());
filter.setAuthenticationFailureHandler(new RestAuthenticationFailureHandler());
}
It looks like there is a lot going on, so if that doesn't solve your issue, feel free to put together a sample, say on GitHub, and I'd be happy to look it over.
I have a Spring Boot application. I have 2 login pages for 2 different sets of users in the system.
Any URL starting with /expert, the user should have the role of either EXPERT or ADMIN. The user uses form login to login into the system, and the login page path is /login.
For some URLs, such as css, js, etc, no authentication is required.
For all other URLs, the user needs no special role, an authentication is enough. The login page for the user should be /loginTwo.
I looked at this stackoverflow question and this documentation to implement this. However, when I access a URL that contains /expert, it takes me to the login page /loginTwo, instead of /login.
Here is my code below:
#Autowired
#Qualifier("userService")
UserDetailsService userDetailsService;
#Bean
public PasswordEncoder passwordEncoder() {
PasswordEncoder encoder = new Md5PasswordEncoder();
return encoder;
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Configuration
#Order(1)
public static class ExpertWebSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login/**").permitAll();
http
.antMatcher("/expert/**")
.authorizeRequests()
.anyRequest().access("hasRole('ROLE_ADMIN') or hasRole('ROLE_EXPERT')")
.and()
.formLogin()
.loginPage("/login").permitAll();
}
}
#Configuration
public static class StudentWebSecurity extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("Here in stu");
http
.authorizeRequests()
.antMatchers("/css/**,/js/**").permitAll()
.antMatchers("/error/**").permitAll()
.antMatchers("/student/**").permitAll()
.antMatchers("/filter/**").permitAll()
.antMatchers("/loginTwo").permitAll()
.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_EXPERT')")
.antMatchers("/").permitAll()
.anyRequest().authenticated()
.and()
.csrf();
http
.formLogin()
.loginPage("/loginTwo").permitAll()
.and()
.logout().permitAll();
http
.sessionManagement()
.maximumSessions(20)
.expiredUrl("/loginTwo")
.maxSessionsPreventsLogin(false);
http
.headers().addHeaderWriter(new XFrameOptionsHeaderWriter(XFrameOptionsMode.SAMEORIGIN));
}
}
Request you to help me with this.
You need to add different authentication filters for each different endpoint.
For HttpSecurity you can define something like this, giving the constructor your UserDetailsService and passing the authentication manager as well:
.addFilterBefore(new FirstLoginFilter("/api/login/first", userDetailsService, authenticationManager()), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new SecondLoginFilter("/api/login/second", userDetailsService, authenticationManager()), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new AdminLoginFilter("/api/login/admin", userDetailsService, authenticationManager()), UsernamePasswordAuthenticationFilter.class)
The implementation for the filters would look something like this.
Let's start with abstract authentication filter which is parent for all the above ones:
public abstract class LoginFilter extends AbstractAuthenticationProcessingFilter {
protected final SimpleUserDetailsService userService;
public LoginFilter(String pattern, SimpleUserDetailsService userService, AuthenticationManager authManager) {
super(new AntPathRequestMatcher(pattern));
this.userService = userService;
this.setAuthenticationManager(authManager);
this.setAuthenticationSuccessHandler(new FormAuthenticationSuccessHandler());
this.setAuthenticationFailureHandler(new FormAuthenticationFailureHandler());
}
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws ServletException {
User authenticatedUser = this.userService.loadUserByUsername(authentication.getName());
UserAuthentication userAuthentication = new UserAuthentication(authenticatedUser);
SecurityContextHolder.getContext().setAuthentication(userAuthentication);
}
}
And for example one of the implementations for the actual filter:
public class FirstLoginFilter extends LoginFilter {
public FirstLoginFilter(String pattern, SimpleUserDetailsService userDetailsService, AuthenticationManager authManager) {
super(pattern, userDetailsService, authManager);
}
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
User user = (User)(new ObjectMapper()).readValue(request.getInputStream(), User.class);
UsernamePasswordAuthenticationToken loginToken = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
Authentication authentication = this.getAuthenticationManager().authenticate(loginToken);
if(!Role.isRolePresent(authentication.getAuthorities(), Role.YOUR_ROLE)) {
throw new BadCredentialsException("Bad credentials");
} else {
return authentication;
}
}
}
My example uses stateless authentication mechanism, so you need to modify the filter accordingly. As I see from your initial example, you are using sessions instead, so in fact it should be much easier for you, since it is already built in to Spring Security
Hope it helps.
I have a problem with default behaviour in spring security with authorize requests provided with Java Config.
http
....
.authorizeRequests()
.antMatchers("/api/test/secured/*").authenticated()
When I do a call to for example /api/test/secured/user without login (with anonymous user), it returns 403 Forbidden. Is there an easy way to change status to 401 Unauthorized when anonymous user wants to get secured by authenticated() or #PreAuthorize resource?
As of Spring Boot 2 class Http401AuthenticationEntryPoint has been removed (see Spring Boot Issue 10725).
Instead of Http401AuthenticationEntryPoint use HttpStatusEntryPoint with HttpStatus.UNAUTHORIZED:
http.exceptionHandling()
.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
With spring security 4.x there is already a class for that
org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint
Spring boot also includes one
org.springframework.boot.autoconfigure.security.Http401AuthenticationEntryPoint
and both benefits that they require the developer to use spec compliant as 401 responses requires that header WWW-Authenticate must be set, example 401 response could be:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="example",
error="invalid_token",
error_description="The access token expired"
So in your security configuration you define and autowire a bean of class
So for instance with spring boot app:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
#Bean
public Http401AuthenticationEntryPoint securityException401EntryPoint(){
return new Http401AuthenticationEntryPoint("Bearer realm=\"webrealm\"");
}
...
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login").anonymous()
.antMatchers("/").anonymous()
.antMatchers("/api/**").authenticated()
.and()
.csrf()
.disable()
.headers()
.frameOptions().disable()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.logout()
.permitAll()
.exceptionHandling().authenticationEntryPoint(securityException401EntryPoint());
}
the relevant line is:
.exceptionHandling().authenticationEntryPoint(securityException401EntryPoint());
I've got solution here:
http
.authenticationEntryPoint(authenticationEntryPoint)
AuthenticationEntryPoint source code:
#Component
public class Http401UnauthorizedEntryPoint implements AuthenticationEntryPoint {
private final Logger log = LoggerFactory.getLogger(Http401UnauthorizedEntryPoint.class);
/**
* Always returns a 401 error code to the client.
*/
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException arg2) throws IOException,
ServletException {
log.debug("Pre-authenticated entry point called. Rejecting access");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Access Denied");
}
}
A simple approach in Spring Boot 2 using lambda expressions:
#Override
public void configure(HttpSecurity http) throws Exception {
http.
...
.exceptionHandling()
.authenticationEntryPoint((request, response, e) -> {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType("application/json");
response.getWriter().write("{ \"error\": \"You are not authenticated.\" }");
})
...
}
You need to extend AuthenticationEntryPoint to do customization based upon the exceptions.
#ControllerAdvice
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
// 401
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication Failed");
}
#ExceptionHandler (value = {AccessDeniedException.class})
public void commence(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException {
// 401
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authorization Failed : " + accessDeniedException.getMessage());
}
}
Specify the above custom AuthenticationEntryPoint in your SecurityConfig like below:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity (prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(new MyAuthenticationEntryPoint());
}
}
Who interested in mechanism of work. If you don't set http.exceptionHandling().authenticationEntryPoint() spring will use defaultAuthenticationEntryPoint() and method ExceptionHandlingConfigurer.createDefaultEntryPoint() will return new Http403ForbiddenEntryPoint()
So, just create Http401UnauthorizedEntryPoint(). Above answers how to do it, didn't duplicate it.
P.S. It's actual for Spring Security 5.2.5.RELEASE