Spring security configuration with JWT / antMatchers blocking access - java

We are setting up Spring Security within a 1.3 Spring Boot application. We have created a class to configure everything with Java config but for some reason every time I try to access to any of the URLS that are configured to "permitAll()" I get a message response similar to this one:
{
"timestamp": 1443099232454,
"status": 403,
"error": "Forbidden",
"message": "Access Denied",
"path": "/api/register"
}
I am not really sure why I get this if I am setting up the antMatchers to allow access to the registration, authentication and activation urls. If I disable those three lines I am able to access these three endpoints.
This is my current configuration:
SecurityConfig.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
#Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Inject
private Http401UnauthorizedEntryPoint authenticationEntryPoint;
#Inject
private UserDetailsService userDetailsService;
#Inject
private TokenProvider tokenProvider;
public SecurityConfig() {
super(true);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Inject
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.csrf()
.disable()
.headers()
.frameOptions()
.disable()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/register").permitAll()
.antMatchers("/api/activate").permitAll()
.antMatchers("/api/authenticate").permitAll()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.apply(securityConfigurerAdapter());
// #formatter:on
}
private JwtTokenConfigurer securityConfigurerAdapter() {
return new JwtTokenConfigurer(tokenProvider);
}
}
UserDetailsService.java
#Service("userDetailsService")
#Log4j2
public class UserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService {
#Inject
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(final String email) {
log.debug("Authenticating {}", email);
String lowercaseEmail = email.toLowerCase();
Optional<User> userFromDatabase = userRepository.findOneByEmail(lowercaseEmail);
return userFromDatabase.map(
user -> {
if (!user.isEnabled()) {
throw new DisabledException("User " + lowercaseEmail + " is disabled");
}
List<GrantedAuthority> grantedAuthorities = user.getRoles().stream()
.map(role -> role.getGrantedAuthority()).collect(Collectors.toList());
return new org.springframework.security.core.userdetails.User(lowercaseEmail, user.getPassword(),
grantedAuthorities);
}).orElseThrow(
() -> new UsernameNotFoundException("User " + lowercaseEmail + " was not found in the database"));
}
}
JwtTokenConfigurer.java
public class JwtTokenConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private TokenProvider tokenProvider;
public JwtTokenConfigurer(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
#Override
public void configure(HttpSecurity http) throws Exception {
JwtTokenFilter customFilter = new JwtTokenFilter(tokenProvider);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}
JwtTokenFilter.java
public class JwtTokenFilter extends GenericFilterBean {
private final static String JWT_TOKEN_HEADER_NAME = "Authorization";
private TokenProvider tokenProvider;
public JwtTokenFilter(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
try {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String jwtToken = httpServletRequest.getHeader(JWT_TOKEN_HEADER_NAME);
if (StringUtils.hasText(jwtToken)) {
String authorizationSchema = "Bearer";
if (jwtToken.indexOf(authorizationSchema) == -1) {
throw new InsufficientAuthenticationException("Authorization schema not found");
}
jwtToken = jwtToken.substring(authorizationSchema.length()).trim();
JwtClaims claims = tokenProvider.parseToken(jwtToken);
String email = (String) claims.getClaimValue(TokenConstants.EMAIL.name());
List<GrantedAuthority> grantedAuthorities = claims.getStringListClaimValue(TokenConstants.ROLES.name())
.stream().map(role -> new SimpleGrantedAuthority(role)).collect(Collectors.toList());
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
email, null, grantedAuthorities);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
filterChain.doFilter(servletRequest, servletResponse);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
Http401UnauthorizedEntryPoint.java
#Component
public class Http401UnauthorizedEntryPoint implements AuthenticationEntryPoint {
#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");
}
}
As I mentioned before, every time I try to access any of these three endpoints:
.antMatchers("/api/register").permitAll()
.antMatchers("/api/activate").permitAll()
.antMatchers("/api/authenticate").permitAll()
I get access denied... Any ideas?

You need to allow anonymous users.
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.csrf()
.disable()
.headers()
.frameOptions()
.disable()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/register").permitAll()
.antMatchers("/api/activate").permitAll()
.antMatchers("/api/authenticate").permitAll()
.and()
.anonymous()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.apply(securityConfigurerAdapter());
// #formatter:on
}
Because AbstractSecurityInterceptor always asks if there is something in the SecurityContextHolder.
AbstractSecurityInterceptor#beforeInvocation line 221
if (SecurityContextHolder.getContext().getAuthentication() == null) {
credentialsNotFound(messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound","An Authentication object was not found in the SecurityContext"),object, attributes);
}

Related

Spring Security hasAuthority is not working properly

I have 3 roles Super Admin, Admin and User. I redirect to two different pages after login. If Admin or Super Admin logged in, it will redirect to /dashboard. If User logs in, it will redirect to /pos. But after Admin and User logged in, it shows 403 page every time. I don't understand why.
#Configuration
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService userDetailsService;
#Autowired
private AuthenticationSuccessHandler authenticationSuccessHandler;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
String loginPage = "/login";
String logoutPage = "/logout";
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
.and()
.authorizeRequests()
.antMatchers(loginPage).permitAll()
.antMatchers("/dashboard", "/boxes/**", "/manufacturers/**", "/brands/**",
"/stocks/**", "/suppliers/**", "/saleinvoices/**", "/purchaseinvoices/**",
"/purchases/**", "/sales/**", "/returns/**", "/users/**").hasRole("ADMIN")
.antMatchers("/dashboard", "/stocks/**", "/pos").hasRole("USER")
.antMatchers("/**").hasAuthority("SUPER_ADMIN")
.anyRequest().authenticated()
.and()
.csrf().disable()
.formLogin()
.loginPage(loginPage).permitAll()
.loginPage("/")
.failureUrl("/login?error=true")
.successHandler(authenticationSuccessHandler)
.usernameParameter("username")
.passwordParameter("password")
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher(logoutPage))
.logoutSuccessUrl(loginPage)
.and()
.exceptionHandling();
}
}
#Component
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
Set<String> roles = AuthorityUtils.authorityListToSet(authentication.getAuthorities());
if (roles.contains("USER")) {
httpServletResponse.sendRedirect("/pos");
} else {
httpServletResponse.sendRedirect("/dashboard");
}
}
}
you can use hasAnyRole() and allow ADMIN and SUPER_ADMIN to access the dashboard

Unauthorization in SpringBoot

Can someone tell me why after loggedin to system, in Postman response i get 401 - unauthorization instead of my token?
CorsConfig.java
#Configuration
public class CorsConfig {
#Bean
public CorsFilter corsFilter(){
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
// config.addAllowedOrigin("*");
// config.addAllowedHeader("*");
// config.addAllowedMethod("*");
source.registerCorsConfiguration("/api/**", config);
return new CorsFilter(source);
}
}
SecurityConfig.java
#Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
#Bean(BeanIds.AUTHENTICATION_MANAGER)
#Override
public AuthenticationManager authenticationManagerBean() throws Exception{
return super.authenticationManagerBean();
}
#Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception{
authenticationManagerBuilder
.userDetailsService(securityUserDetailsService)
.passwordEncoder(passwordEncoder());
}
#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/**", "/api/user/register/**", "/api/user/namecheck/**", "/api/password/**")
// .permitAll()
// .antMatchers("/api/user/checkUsernameAvailability", "/api/user/checkEmailAvailability")
// .permitAll()
//.antMatchers(HttpMethod.GET, "/api/polls/**", "/api/users/**")
// .permitAll()
.anyRequest()
.authenticated();
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
http.headers().cacheControl();
}
}
AuthController.java
#RestController
#RequestMapping("/api/auth")
public class AuthController {
#Autowired
AuthenticationManager authenticationManager;
#Autowired
JwtTokenProvider tokenProvider;
#PostMapping
public ResponseEntity<?> authenticateUser(#Valid #RequestBody AuthRequest request){
Object principal;
Object credentials;
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
request.getEmail(),
request.getPassword()
));
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = tokenProvider.generateToken(authentication);
return ResponseEntity.ok(new JwtAuthenticationResponse(jwt));
}
}
AuthenticatedUser.java, my model
#Data
#Accessors(chain = true)
#EqualsAndHashCode
public class AuthenticatedUser implements UserDetails {
private UUID id;
private String firstName;
private String lastName;
private String token;
#JsonIgnore
private String email;
#JsonIgnore
private String password;
public AuthenticatedUser(){}
public static AuthenticatedUser mapFromEntity(UserEntity userEntity){
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(userEntity.getRole().name()));
return new AuthenticatedUser()
.setId(userEntity.getId())
.setEmail(userEntity.getEmail())
.setPassword(userEntity.getPassword())
.setFirstName(userEntity.getFirstName())
.setLastName(userEntity.getLastName())
.setAuthorities(authorities);
}
private Collection<? extends GrantedAuthority> authorities;
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
TokenProvider.java
#Component
public class JwtTokenProvider {
#Value("${jwt.salt}")
private String jwtSalt;
#Value("${jwt.expiry}")
private int jwtExpiry;
public String generateToken(Authentication authentication) {
AuthenticatedUser userEntity = (AuthenticatedUser) authentication.getPrincipal();
Date now = new Date();
Date expiryDate = new Date(now.getTime() + jwtExpiry);
return Jwts.builder()
.setSubject(userEntity.getId().toString())
.setIssuedAt(new Date())
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, jwtSalt)
.compact();
}
public UUID getUserIdFromJWT(String token) {
Claims claims = Jwts.parser()
.setSigningKey(jwtSalt)
.parseClaimsJws(token)
.getBody();
return UUID.fromString(claims.getSubject());
}
public boolean validateToken(String authToken) {
///
JwtauthenticationFilter.java
public class JwtAuthenticationFilter extends OncePerRequestFilter {
#Autowired
private JwtTokenProvider tokenProvider;
#Autowired
private SecurityUserDetailsService securityUserDetailsService;
#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)) {
UUID userId = tokenProvider.getUserIdFromJWT(jwt);
UserDetails userDetails = securityUserDetailsService.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");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
You should /api/auth route to the SecurityConfig
#Override
protected void configure(HttpSecurity http) throws Exception{
http
.cors()
.and()
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/",
"/api/auth",
"/favicon.ico",
"/**/*.png",
"/**/*.gif",
"/**/*.svg",
"/**/*.jpg",
"/**/*.html",
"/**/*.css",
"/**/*.js")
.permitAll()
.authenticated();
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
http.headers().cacheControl();
}
Because I can't comment, I'm writing it here.
Make sure like #Pankratiew Alexandr said to add /api/auth endpoint with permitAll().
Then my question is if you have any user already in your memory or database. I mean that when you call method authenticateUser in your controller you use authenticationManager.authenticate(..., so loadByUsername() is called and it may don't load any user with provided e-mail and password.

Spring security mutiple login process doesn't work it supposed to do

I'm suffering for multiple login processing.
I've searched a lot about it and none of answers out there are working.
I wrote two login processing one for admin and the other one for normal user.
And also wrote each success and failure handlers but the handlers are always working with the last one in #order(2) configuration even though I request to /admin.
My problems are:
I can login but the success handler is always triggered as the last one in #order(2).
The failure handler is triggered but as the last one as well regardless what URL I request and it throws 404 error (I can see it goes on proper controller during debugging). Maybe tiles doesn't work on failure process?
Here is my security config:
#Configuration
#EnableWebSecurity
#AllArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private UsersServiceImpl usersService;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(
"/css/**",
"/js/**",
"/img/**",
"/font/**",
"/html/**",
"/jusoPopup",
"favicon.ico"
);
}
#Configuration
#Order(1)
#NoArgsConstructor
public static class AdminConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/admin/**")
.authorizeRequests()
.anyRequest().hasRole("ADMIN")
.and()
.formLogin()
.loginPage("/admin/login")
.defaultSuccessUrl("/admin")
.failureHandler(adminFailureHandler())
.permitAll()
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/")
.invalidateHttpSession(true)
.and()
.csrf().disable();
}
#Bean
public AuthenticationSuccessHandler adminSuccessHandler() {
return new CustomLoginSuccessHandler("/admin");
}
#Bean
public AuthenticationFailureHandler adminFailureHandler() {
return new CustomLoginFailureHandler("/admin/login?error=true");
}
}
#Configuration
#Order(2)
#NoArgsConstructor
public static class NormalConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/Ticketing/**", "/**/write").hasRole("MEMBER")
.anyRequest().permitAll()
.and()
.formLogin()
.loginPage("/login")
.successHandler(successHandler())
.failureHandler(failureHandler())
.permitAll()
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/")
.invalidateHttpSession(true)
.and()
.headers()
.frameOptions().sameOrigin()
.and()
.csrf().disable();
}
#Bean
public AuthenticationSuccessHandler successHandler() {
return new CustomLoginSuccessHandler("/");
}
#Bean
public AuthenticationFailureHandler failureHandler() {
return new CustomLoginFailureHandler("/login?error=true");
}
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(usersService).passwordEncoder(passwordEncoder());
}
}
class CustomLoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
public CustomLoginSuccessHandler(String defaultTargetUrl) {
setDefaultTargetUrl(defaultTargetUrl);
}
#Override
public void onAuthenticationSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication
) throws ServletException, IOException {
HttpSession session = request.getSession();
if (session != null) {
String redirectUrl = (String) session.getAttribute("prevPage");
if (redirectUrl != null) {
session.removeAttribute("prevPage");
getRedirectStrategy().sendRedirect(request, response, redirectUrl);
} else {
super.onAuthenticationSuccess(request, response, authentication);
}
} else {
super.onAuthenticationSuccess(request, response, authentication);
}
}
}
#Getter
#Setter
#AllArgsConstructor
class CustomLoginFailureHandler implements AuthenticationFailureHandler {
private String defaultFailureUrl;
#Override
public void onAuthenticationFailure(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception
) throws IOException, ServletException {
String errorMessage = "some error message";
request.setAttribute("errorMessage", errorMessage);
request.getRequestDispatcher(defaultFailureUrl).forward(request, response);
}
}
It's hard to find proper working well multiple login processing resource out there if you guys know the good resource about it please let me know.
I hope it's my code error so that I don't have to change the application structure.
I read some Baeldung guide and spring security documents Here is my edited configuration :
#EnableWebSecurity
public class SecurityConfig{
#NoArgsConstructor
#Configuration
#Order(1)
public static class AdminConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/admin/**")
.authorizeRequests().anyRequest().hasRole("ADMIN")
.and()
.formLogin()
.loginPage("/admin/login")
.defaultSuccessUrl("/admin")
.failureHandler(adminFailureHandler())
.permitAll()
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/")
.invalidateHttpSession(true)
.and()
.csrf().disable();
}
#Bean
public AuthenticationEntryPoint authenticationEntryPoint(){
BasicAuthenticationEntryPoint entryPoint =
new BasicAuthenticationEntryPoint();
entryPoint.setRealmName("admin realm");
return entryPoint;
}
#Bean
public AuthenticationFailureHandler adminFailureHandler() {
return new CustomLoginFailureHandler("/admin/login");
}
}
#AllArgsConstructor
#Configuration
public static class NormalConfigurationAdapter extends WebSecurityConfigurerAdapter {
private UsersServiceImpl usersService;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers(
"/css/**",
"/js/**",
"/img/**",
"/font/**",
"/html/**",
"/jusoPopup",
"favicon.ico"
);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/Ticketing/**", "**/write")
.hasRole("MEMBER")
.anyRequest().permitAll()
.and()
.formLogin()
.loginPage("/login")
.successHandler(successHandler())
.failureHandler(failureHandler())
.permitAll()
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/")
.invalidateHttpSession(true)
.and()
.headers().frameOptions().sameOrigin()
.and()
.csrf().disable();
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(usersService).passwordEncoder(passwordEncoder());
}
#Bean
public AuthenticationSuccessHandler successHandler() {
return new CustomLoginSuccessHandler("/");
}
#Bean
public AuthenticationFailureHandler failureHandler() {
return new CustomLoginFailureHandler("/login");
}
}
}
class CustomLoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
public CustomLoginSuccessHandler(String defaultTargetUrl) {
setDefaultTargetUrl(defaultTargetUrl);
}
#Override
public void onAuthenticationSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication
) throws ServletException, IOException {
HttpSession session = request.getSession();
if (session != null) {
String redirectUrl = (String) session.getAttribute("prevPage");
if (redirectUrl != null) {
session.removeAttribute("prevPage");
getRedirectStrategy().sendRedirect(request, response, redirectUrl);
} else {
super.onAuthenticationSuccess(request, response, authentication);
}
} else {
super.onAuthenticationSuccess(request, response, authentication);
}
}
}
#Getter
#Setter
#AllArgsConstructor
class CustomLoginFailureHandler implements AuthenticationFailureHandler {
private String defaultFailureUrl;
#Override
public void onAuthenticationFailure(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception
) throws IOException, ServletException {
String errorMessage = "Error";
request.setAttribute("errorMessage", errorMessage);
request.getRequestDispatcher(defaultFailureUrl).forward(request, response);
}
}
It looks more organized than before i guess but still has the same problem... also read about AuthenticationEntryPoint but not sure it's fit in my case

Spring Boot REST API 401 on public endpoint?

I am getting 401 Unauthorized on not secured endpoint responsible for registering:
this is my Config class I use:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
//#FieldDefaults(level = PRIVATE, makeFinal = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final RequestMatcher PUBLIC_URLS = new OrRequestMatcher(
new AntPathRequestMatcher("/public/**")
);
private static final RequestMatcher PROTECTED_URLS = new NegatedRequestMatcher(PUBLIC_URLS);
#Autowired
TokenAuthenticationProvider provider;
SecurityConfig() {
super();
System.out.println("XXXXXXXXXXXX");
System.out.println("XXXXXXXXXXXX");
//this.provider = requireNonNull(provider);
}
#Override
protected void configure(final AuthenticationManagerBuilder auth) {
auth.authenticationProvider(provider);
}
#Override
public void configure(final WebSecurity web) {
web.ignoring().requestMatchers(PUBLIC_URLS);
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(STATELESS)
.and()
.exceptionHandling()
// this entry point handles when you request a protected page and you are not yet
// authenticated
.defaultAuthenticationEntryPointFor(forbiddenEntryPoint(), PROTECTED_URLS)
.and()
.authenticationProvider(provider)
.addFilterBefore(restAuthenticationFilter(), AnonymousAuthenticationFilter.class)
.authorizeRequests()
.requestMatchers(PROTECTED_URLS)
.authenticated()
.and()
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.logout().disable();
}
#Bean
TokenAuthenticationProvider tokenAuthenticationProvider() {
return new TokenAuthenticationProvider();
}
#Bean
TokenAuthenticationFilter restAuthenticationFilter() throws Exception {
final TokenAuthenticationFilter filter = new TokenAuthenticationFilter(PROTECTED_URLS);
filter.setAuthenticationManager(authenticationManager());
filter.setAuthenticationSuccessHandler(successHandler());
return filter;
}
#Bean
SimpleUrlAuthenticationSuccessHandler successHandler() {
final SimpleUrlAuthenticationSuccessHandler successHandler = new SimpleUrlAuthenticationSuccessHandler();
successHandler.setRedirectStrategy(new NoRedirectStrategy());
return successHandler;
}
/**
* Disable Spring boot automatic filter registration.
*/
#Bean
FilterRegistrationBean disableAutoRegistration(final TokenAuthenticationFilter filter) {
final FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}
#Bean
AuthenticationEntryPoint forbiddenEntryPoint() {
return new HttpStatusEntryPoint(FORBIDDEN);
}
}
So for now I have public endpoints responsible for registering and login, but I cannot access them via Postman and browser.
Is something wrong with implementing this config class? What can cause this problem?
Use configure(WebSecurity web) for publicly accessible endpoints, it will not apply the security filter chain for specified endpoints.
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/public/**")
}
OR Try to change the order for configure(final HttpSecurity http). Add .requestMatchers(PROTECTED_URLS) before the filters and entrypoint.
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.requestMatchers(PROTECTED_URLS)
.authenticated()
.and()
.exceptionHandling()
.defaultAuthenticationEntryPointFor(forbiddenEntryPoint(), PROTECTED_URLS)
.and()
.sessionManagement()
.sessionCreationPolicy(STATELESS)
.and()
.authenticationProvider(provider)
.addFilterBefore(restAuthenticationFilter(), AnonymousAuthenticationFilter.class)
.formLogin().disable()
.httpBasic().disable()
.logout().disable();
}

Angular 6 Basic Auth returns 401 from client

So I've looked around for the answer to my problem for quite a while now and tried many suggestions but I can't seem to find an answer.
The problem is, when I use Postman to check if basic auth works I get a 200 code back and it's all good, but as soon as I try to authenticate using my Login Component I get the code 401 back and says "Full authentication is required to access this resource".
I'm fairly new to Angular and completely new to using Basic Auth so I have no idea why does it work with Postman and why doesn't it work from the app.
Any help is appreciated
Below are the relevant codes
log-in.component.ts:
onLogin(form: NgForm) {
/* ... */
let headers = new Headers();
let userCredentials = user.userName + ":" + user.password;
headers.append("Origin", "http://localhost:8080");
headers.append("Authorization", "Basic " + btoa(userCredentials));
return this.http.post('http://localhost:8080/api/users/login', headers).subscribe(
(response) => {
/* ... */
},
(error) => {
console.log(error);
}
);
}
Endpoint on the server side:
#PostMapping(LOG_IN)
public ResponseEntity<User> login() {
return ResponseEntity.ok().build();
}
WebSecurityConfig:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/h2/**").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic()
.authenticationEntryPoint(getBasicAuthEntryPoint())
.and()
.headers()
.frameOptions().disable()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Autowired
protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("admin").password("1234").roles("ADMIN");
}
#Autowired
private UserDetailsService userDetailsService;
#Autowired
protected void configureAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean
public CustomBasicAuthenticationEntryPoint getBasicAuthEntryPoint(){
return new CustomBasicAuthenticationEntryPoint();
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
CustomBasicAuthenticationEntryPoint:
public class CustomBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {
#Override
public void commence(final HttpServletRequest request,
final HttpServletResponse response,
final AuthenticationException authException) throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.addHeader("WWW-Authenticate", "Basic realm=" + getRealmName() + "");
PrintWriter writer = response.getWriter();
writer.println("HTTP Status 401 : " + authException.getMessage());
}
#Override
public void afterPropertiesSet() throws Exception {
setRealmName("MY REALM");
super.afterPropertiesSet();
}
}
MyUserDetailsService:
#Service
public class MyUserDetailsService implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Autowired
private AuthenticatedUser authenticatedUser;
#Override
#Transactional(readOnly = true)
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<User> oUser = userRepository.findByUserName(username);
if (!oUser.isPresent()) {
throw new UsernameNotFoundException(username);
}
User user = oUser.get();
authenticatedUser.setUser(user);
Set<GrantedAuthority> grantedAuthorities = new HashSet<>();
grantedAuthorities.add(new SimpleGrantedAuthority(user.getRole().toString()));
return new org.springframework.security.core.userdetails.User(user.getUserName(), user.getPassword(), grantedAuthorities);
}
}
You need to pass the headers as 3rd parameter for the post method. The 2nd one is the body
return this.http.post('http://localhost:8080/api/users/login', {}, {headers}).subscribe(
(response) => {
If you are using angular 6, you should really be using the new HttpClient class, the old Http class being deprecated
This is because the browser send OPTION method to the server before send your request, , try to update your security configuration by allowing OPTION method. like this
protected void configure(HttpSecurity http) throws Exception
{
http
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS,"/path/to/allow").permitAll()//allow CORS option calls
.antMatchers("/resources/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.httpBasic();
}

Categories

Resources