Oauth Spring Security not unauthorized to microservice oauth, registerController - spring

I am beginner to Spring. I've been with this problem for several days and I wouldn't know how to solve it.
I have two microservices:
Microservice authentication : This allows return a JWT token to user when it access to path: /oauth/token
Microservice Account user : This microservice will have a lot of functions but the problem is it:
The user must be register in platform and this microservice call to server oauth to save the new created user.
So, I create the controller to microservice Oauth:
#PostMapping
#PreAuthorize("#oauth2.hasScope('server')")
public UserDto createUser(#Valid #RequestBody UserDto userDto) {
Usuario savedUser = new Usuario();
try {
savedUser = userService.create(this.toUsuario(userDto));
} catch (ArendheException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return toDto(savedUser);
}
The WebSecurityConfigureAdapter is:
#Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService usuarioService;
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
#Autowired
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(this.usuarioService).passwordEncoder(passwordEncoder());
}
#Bean("authenticationManager")
#Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(HttpMethod.POST,"/oauth/**").permitAll()
.antMatchers(HttpMethod.POST, "/user/**").permitAll()
.anyRequest().authenticated()
.and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
As you can see, I write two antMatchers, the second to create a new user.
The problem is when I test it with Postman (path localhost:8005/user with data JSON parsed to POST method). The output is:
{
"error": "unauthorized",
"error_description": "Full authentication is required to access this resource"
}
I dont understand it because I have a permitAll to /user/** path.
Thanks.

Related

How to configure two security configs with two filters in spring boot correctly?

I've implmemented security in my spring boot microservices project, the requirment is to have
two types of configurations, one for user request (from angular) and one from other services.
The design is to use JWT token for user request and API key for system calls.
Here is the config file (one file) but have also try to split it to two files with no impact:
#Configuration
#EnableWebSecurity
public class SecurityConfig {
#Configuration
#Order(1)
public static class APISecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${my.api.key.header}")
private String principalRequestHeader;
#Value("${my.api.key.token}")
private String principalRequestValue;
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.cors().disable().csrf().disable();
httpSecurity
.antMatcher("/api/users/**")
.authorizeRequests() //
.anyRequest().authenticated()
.and()
.addFilterBefore(new APIKeyAuthFilter(principalRequestHeader, principalRequestValue), UsernamePasswordAuthenticationFilter.class);
}
}
#Order(2)
#Configuration
public static class MySecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsService userDetailsService;
#Bean
public AuthTokenFilter authenticationJwtTokenFilter() {
return new AuthTokenFilter();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/api/users/**");
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.cors().disable().csrf().disable();
httpSecurity
.authorizeRequests()
.antMatchers("/users/UserEmailExist", "/users/User/Add", "/users/Authenticate",
"/users/User/ChangePassword")
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/users/**").hasAnyRole(ROLE_ADMIN_USER, ROLE_MANAGER_USER)
.anyRequest().authenticated()
.and()
.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
}
Each config has a filter attached to it, here the api one:
public class APIKeyAuthFilter extends GenericFilterBean {
private String principalRequestHeader;
private String principalRequestValue;
public APIKeyAuthFilter(String principalRequestHeader, String principalRequestValue) {
super();
this.principalRequestHeader = principalRequestHeader;
this.principalRequestValue = principalRequestValue;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
if(request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
String apiKey = getApiKey((HttpServletRequest) request);
if(apiKey != null) {
if(apiKey.equals(principalRequestValue)) {
ApiKeyAuthenticationToken apiToken = new ApiKeyAuthenticationToken(apiKey, AuthorityUtils.NO_AUTHORITIES);
SecurityContextHolder.getContext().setAuthentication(apiToken);
} else {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setStatus(401);
httpResponse.getWriter().write("Invalid API Key");
return;
}
}
}
chain.doFilter(request, response);
}
}
Here is the filter for jwt (normal user from angular):
public class AuthTokenFilter extends OncePerRequestFilter {
#Autowired
private JwtUtils jwtUtils;
#Autowired
private MyUserDetailsService userDetailsService;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
String jwt = parseJwt(request);
if (jwt != null && jwtUtils.validateJwtToken(jwt)) {
String username = jwtUtils.getUserNameFromJwtToken(jwt);
MSUserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
logger.error("Cannot set user authentication: {}", e);
}
filterChain.doFilter(request, response);
}
}
I've created two different controllers, one with prefix /api/users and second /users.
Here is what happen in two different scenarios:
The user login from Angular, get jwt token and process request which end up in the Jwt filter,
this scenarion looking good with no issues as the user is able to process request as long
he is authenticate.
Microservice send a request with api-key to url with /api/users prefix, it ended up on the same
filter the normal user ended which is not correct and without JWT token he is actually
able to proceed to the controller and process the request without going
to the correct filter.
The only solution I have is to have only one filter and process the header
for api-key and jwt but it doesn't seem right.
I've looked online and try to figure out what I'm doing wrong but no clue as of now.
An update on this issue so I hope it will help to the community.
Firstly, I removed the following code and this mainly fix the problem:
// #Override
// public void configure(WebSecurity web) throws Exception {
// web.ignoring().antMatchers("/api/users/**");
// }
The way the solution work as a whole is that the first configuration #Order(1) you
define .antMatcher which means the configuration will work only for urls that match
the prefix.
So now, scenario 1. User from Angular go the the JWT filter only.
scenario 2. API user will lend in the API filter first! But once it's done (After succesfull authentication) it still
continue to the JWT filter but becuase it doesn't have JWT the filter not doing anything.
I would like to avoid to other filter in case of API call but the solution work,
problem solved.
I must say that security in spring boot is the most complex I came across so far from other features.
Because the AuthTokenFilter is instantiated with #Bean, which causes the filter to be added to the ApplicationFilterChain, after the APIKeyAuthFilter is processed, it can also enter the AuthTokenFilter.

Spring boot + LDAP form login, logout and validating the token after login

I am integrating spring boot with LDAP to Authenticate a user. So that only authenticated users can only access the API.
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
LdapAuthenticationProvider authenticationProvider(final LdapAuthenticator authenticator) {
return new LdapAuthenticationProvider(authenticator);
}
#Bean
BindAuthenticator authenticator(final BaseLdapPathContextSource contextSource) {
final BindAuthenticator authenticator = new BindAuthenticator(contextSource);
authenticator.setUserDnPatterns(new String[] {
"xx" });
return authenticator;
}
#Override
public void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication().userSearchFilter("(sAMAccountName={0})")
.contextSource(contextSource());
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.formLogin().and()
.authorizeRequests()
.anyRequest().fullyAuthenticated().and().logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login");
}
#Override
public void configure(final WebSecurity web) throws Exception {
web.debug(true);
}
#Bean
LdapContextSource contextSource() {
final LdapContextSource contextSource = new LdapContextSource();
contextSource.setUrl("xx);
contextSource.setBase("xx");
contextSource.setUserDn("xx");
contextSource.setPassword("xx");
contextSource.setPooled(true);
contextSource.afterPropertiesSet();
return contextSource;
}
#Bean
public LdapTemplate ldapTemplate() {
final LdapTemplate ldapTemplate = new LdapTemplate(
contextSource());
ldapTemplate.setIgnorePartialResultException(true);
return ldapTemplate;
}
}
I am using the inbuild form login.
Question
Who (which class) is responsible to create a success token and where is it stored and in successive calls how is it validated?
Now I am only redirecting the unauthenticated calls to the login page due to this it giving 200 success responses, How to override this and send 401
Edited:
I have one specific question
If there is no token, the user is stored in the session -> how subsequent requests are validated. Which all classes are used

How to permit endpoints in spring authorization server?

I have a spring boot oauth2 authorization server which will provide and authorize tokens. I also want to provide endpoints for user creation. Can you tell me how to permit these endpoints for non-authenticated users? I tried following configuration:
#Configuration
#EnableAuthorizationServer
#RequiredArgsConstructor
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
private TokenStore tokenStore = new InMemoryTokenStore();
private final UserDetailsService userDetailsServiceImpl;
private final AuthenticationManager authenticationManager;
private final PasswordEncoder passwordEncoder;
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// TODO persist clients details
clients.inMemory()
.withClient("browser")
.authorizedGrantTypes("refresh_token", "password")
.scopes("ui");
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(tokenStore)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsServiceImpl);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.passwordEncoder(passwordEncoder);
}
}
and authorization server configuration:
#Configuration
#EnableWebSecurity
#RequiredArgsConstructor
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsService userDetailsServiceImpl;
#Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests -> {
authorizeRequests
.antMatchers(HttpMethod.POST, "/user/**").permitAll()
.anyRequest().authenticated();
});
}
#Bean(name = "authenticationManager")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsServiceImpl)
.passwordEncoder(passwordEncoder());
}
}
and here is endpoint which I want permit:
#RestController
#RequestMapping(path = "/user")
#RequiredArgsConstructor
public class UserController {
private final UserService userService;
#PostMapping
public UUID create(#RequestBody UserDto userDto) {
return userService.create(userDto);
}
}
With these configurations I always got response:
{
"timestamp": "2019-12-28T16:01:09.135+0000",
"status": 403,
"error": "Forbidden",
"message": "Forbidden",
"path": "/user"
}
I am using spring boot 2. Thank you in advice.
You need to disable CSRF in your AuthorizationConfig class. Try this configuration :
http.authorizeRequests(authorizeRequests -> {
authorizeRequests
.antMatchers(HttpMethod.POST, "/user/**").permitAll()
.anyRequest().authenticated();
}).csrf(csrf -> {
csrf.disable();
});
For more information about CSRF, check this website: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF).
Basically, you don't want to allow anybody to POST information on your website, so you allow to POST only if the user can provide a token indicating that he is using your website to POST (the token is provided by your server). In many web applications now, you can disable it, as you are POSTing from many locations... But don't forget the security of your website.

Spring - Add a check for 3rd parameter during authentication

At a glance, I have API Back-end App written in Spring Boot which uses JWT for secured data transmission. I want to add 3rd parameter for authorization, so I should have login, password and storeID parameters. I am inspired by this answer How implement Spring security when login page having more field apart from user name and password? but when I followed proposed solution my 3rd parameter in not used. My impression is that I am missing something important in Security Config. Could you please point to my mistake?
SecurityConfig
#SuppressWarnings("SpringJavaAutowiringInspection")
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private AuthenticationDetailsSource<HttpServletRequest, ?> webAuthenticationDetailsSourceImpl;
#Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.authenticationProvider(myAuthProvider());
}
#Bean
public CustomUserDetailsAuthenticationProvider myAuthProvider() throws Exception {
CustomUserDetailsAuthenticationProvider provider = new CustomUserDetailsAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder());
provider.setUserDetailsService(userDetailsService);
return provider;
}
#Bean
public UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter() throws Exception {
UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter = new UsernamePasswordAuthenticationFilter();
usernamePasswordAuthenticationFilter.setAuthenticationManager(authenticationManager());
usernamePasswordAuthenticationFilter.setAuthenticationDetailsSource(webAuthenticationDetailsSourceImpl);
return usernamePasswordAuthenticationFilter;
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
JwtAuthenticationTokenFilter authenticationTokenFilter = new JwtAuthenticationTokenFilter();
authenticationTokenFilter.setAuthenticationManager(authenticationManagerBean());
return authenticationTokenFilter;
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// we don't need CSRF because our token is invulnerable
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// don't create session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
// allow anonymous resource requests
.antMatchers(
HttpMethod.GET,
"/",
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js"
).permitAll()
.antMatchers("/auth/**").permitAll()
.anyRequest().authenticated();
// Custom JWT based security filter
httpSecurity
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
// disable page caching
httpSecurity.headers().cacheControl();
}
}
I was under impression I can check against storeID field in WebAuthenticationDetailsSourceImpl, but looks like it has never been executed because I don't see anything related in log.
WebAuthenticationDetailsSourceImpl:
#Component
public class WebAuthenticationDetailsSourceImpl implements AuthenticationDetailsSource<HttpServletRequest, JwtAuthenticationRequest> {
#Override
public JwtAuthenticationRequest buildDetails(HttpServletRequest context) {
System.out.println("___#####_____");
System.out.println(context);
System.out.println("___#####_____");
return new JwtAuthenticationRequest();
}
}
cuz you don't insert "your" usernamePasswordAuthenticationFilter that set webAuthenticationDetailsSourceImpl to Spring Security's authentication filter chain.
perhaps current your authentication filter chain is
~
JwtAuthenticationTokenFilter
(Spring Security's original)UsernamePasswordAuthenticationFilter
~
hence,if you want to retrieve your additional parameter in "your" usernamePasswordAuthenticationFilter add this filter too like a JwtAuthenticationTokenFilter
but , if you want to simply retrieve parameter at JwtAuthenticationTokenFilter
use setAuthenticationDetailsSource at there
#Bean
public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
JwtAuthenticationTokenFilter authenticationTokenFilter = new JwtAuthenticationTokenFilter();
authenticationTokenFilter.setAuthenticationManager(authenticationManagerBean());
authenticationTokenFilter.setAuthenticationDetailsSource(webAuthenticationDetailsSourceImpl);
return authenticationTokenFilter;
}

ProviderManager.authenticate called twice for BadCredentialsException

Spring 4.1 and Spring Security 3.2:
We implemented a Custom Authentication Provider, that throws a BadCredentialsException if user enters an incorrect password.
When the BadCredentialsException is thrown, the ProviderManager.authenticate method is called, which calls the authenticate method in the Custom Authentication again. When a LockedException is thrown, the authenicate method in the Custom Authentication Provider is not called again. We are planning on keeping a count of number of login attempts, so we don't want the authenticate method called twice. Does anyone know why the authenticate method in the custom authentication class would be called twice?
WebConfig:
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
#Autowired
private AMCiUserDetailsService userDetailsService;
#Autowired
private CustomImpersonateFailureHandler impersonateFailureHandler;
#Autowired
private LoginFailureHandler loginFailureHandler;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/jsp/*.css","/jsp/*.js","/images/**").permitAll()
.antMatchers("/login/impersonate*").access("hasRole('ADMIN') or hasRole('ROLE_PREVIOUS_ADMINISTRATOR')")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.jsp")
.defaultSuccessUrl("/jsp/Home.jsp",true)
.loginProcessingUrl("/login.jsp")
.failureHandler(loginFailureHandler)
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/login.jsp?msg=1")
.permitAll()
.and()
.addFilter(switchUserFilter())
.authenticationProvider(customAuthenticationProvider);
http.exceptionHandling().accessDeniedPage("/jsp/SecurityViolation.jsp"); //if user not authorized to a page, automatically forward them to this page.
http.headers().addHeaderWriter(new XFrameOptionsHeaderWriter(XFrameOptionsMode.SAMEORIGIN));
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
//Used for the impersonate functionality
#Bean CustomSwitchUserFilter switchUserFilter() {
CustomSwitchUserFilter filter = new CustomSwitchUserFilter();
filter.setUserDetailsService(userDetailsService);
filter.setTargetUrl("/jsp/Impersonate.jsp?msg=0");
filter.setSwitchUserUrl("/login/impersonate");
filter.setExitUserUrl("/logout/impersonate");
filter.setFailureHandler(impersonateFailureHandler);
return filter;
}
}
Custom Authentication Provider:
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired(required = true)
private HttpServletRequest request;
#Autowired
private AMCiUserDetailsService userService;
#Autowired
private PasswordEncoder encoder;
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName().trim();
String password = ((String) authentication.getCredentials()).trim();
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
throw new BadCredentialsException("Login failed! Please try again.");
}
UserDetails user;
try {
user = userService.loadUserByUsername(username);
//log successful attempt
auditLoginBean.setComment("Login Successful");
auditLoginBean.insert();
} catch (Exception e) {
try {
//log unsuccessful attempt
auditLoginBean.setComment("Login Unsuccessful");
auditLoginBean.insert();
} catch (Exception e1) {
// TODO Auto-generated catch block
}
throw new BadCredentialsException("Please enter a valid username and password.");
}
if (!encoder.matches(password, user.getPassword().trim())) {
throw new BadCredentialsException("Please enter a valid username and password.");
}
if (!user.isEnabled()) {
throw new DisabledException("Please enter a valid username and password.");
}
if (!user.isAccountNonLocked()) {
throw new LockedException("Account locked. ");
}
Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
List<GrantedAuthority> permlist = new ArrayList<GrantedAuthority>(authorities);
return new UsernamePasswordAuthenticationToken(user, password, permlist);
}
public boolean supports(Class<? extends Object> authentication) {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
The reason is that you add your authentication provider twice, one time in configure(HttpSecurity) and one time in configure(AuthenticationManagerBuilder). This will create a ProviderManager with two items, both being your provider.
When authentication is processed, the providers will be asked in order until a success is made, unless a LockedException or similar status exception is thrown, then the loop will break.
There may be a situtation which you don't override configure(AuthenticationManagerBuilder) and still same AuthenticationProver's authenticate method gets called twice like Phil mentioned in his comment in the accepted answer.
Why is that?
The reason is that when you don't override configure(AuthenticationManagerBuilder) and have an AuthenticationProvider bean, it will be registered by Spring Security, you don't have to do anything else.
However, when configure(AuthenticationManagerBuilder) overridden, Spring Security will invoke it and won't try to register any provider by itself.
If you're curious, you can take a look the related method. disableLocalConfigureAuthenticationBldr is true if you override configure(AuthenticationManagerBuilder).
So, briefly, if you want to register just one custom AuthenticationProvider then do not override configure(AuthenticationManagerBuilder), do not call authenticationProvider(AuthenticationProvider) in configure(HttpSecurity), just make your AuthenticationProviver implementation bean by annotating #Component and you're good to go.

Resources