It looks very strange behavior, probably a bug.
I have a Spring boot 1.3.2 as backend API using Rest Service and I have another application using Angular 2 consuming these services.
All the security stuff is working ok with JWT Token, I can restricted my services for logged users, I can check the user logged and so on. Authorization is not working 100%, If I add on my services #Secured or #PreAuthorize with some user role this work with swagger and with my MockMvc tests using #WithMockUser(roles="ROLE_TEST") so it's configured OK.
The problem is that Authorization with #Secured or #PreAuthorize is not working when I'm accessing via angular application, All my requests that has #Secured or #PreAuthorize I receive Status 403.
authentication.getAuthorities() that all my roles is being loaded perfectly
Controller:
#RequestMapping(method=RequestMethod.GET, produces=MediaType.APPLICATION_JSON_UTF8_VALUE)
#Secured("ROLE_MANTER_INSTITUICAO")
public List<HierarquiaInstituicao> getAll() {
return service.findAll();
}
Security Config:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled=true, jsr250Enabled=true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
private UserService userService;
private final TokenAuthenticationService tokenAuthenticationService;
public SpringSecurityConfig() {
super(true);
this.userService = new UserService();
tokenAuthenticationService = new TokenAuthenticationService(userService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling().and().anonymous().and().servletApi().and()
.authorizeRequests()
// Allow anonymous logins
.antMatchers("/api/auth/**").permitAll()
// All other request need to be authenticated
.anyRequest().authenticated().and()
// Custom Token based authentication based on the header
// previously given to the client
.addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService),
UsernamePasswordAuthenticationFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
#Override
public UserService userDetailsService() {
return userService;
}
#Bean
public TokenAuthenticationService tokenAuthenticationService() {
return tokenAuthenticationService;
}
}
My filter:
public class StatelessAuthenticationFilter extends GenericFilterBean {
private final TokenAuthenticationService authenticationService;
public StatelessAuthenticationFilter(TokenAuthenticationService authenticationService) {
this.authenticationService = authenticationService;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
Authentication authentication = authenticationService.getAuthentication(httpRequest);
if(authentication != null) {
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
}
else {
httpResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
}
SecurityContextHolder.getContext().setAuthentication(null);
}
}
Related
I need to configure the Spring Security so that users that have either Basic Auth or API Key can access the API in the absence of one other. The below code works for Basic Auth but when I switch to API Key in Postman, I can not access the API using the correct API key/value added to the Header in Postman in the Authorization tab and I am getting 401.
However, when I just use ApiKeyWebSecurityConfigurerAdapter by itself, I can access the API even with the wrong API key/value pair. Any help would be appreciated.
Note: I used this and https://docs.spring.io/spring-security/site/docs/3.2.x/reference/htmlsingle/html5/#multiple-httpsecurity as reference.
Security Config:
#EnableWebSecurity
public class MultiHttpSecurityConfig {
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().and()
.userDetailsService(userDetailsService())
.httpBasic(Customizer.withDefaults());
}
#Bean
public UserDetailsService userDetailsService() {
return new JpaUserDetailService();
}
#Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
#Configuration
public static class ApiKeyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Value("${myApp.http.auth-token-header-name}")
private String principalRequestHeader;
#Value("${myApp.http.auth-token}")
private String principalRequestValue;
#Override
protected void configure(HttpSecurity http) {
http
.csrf()
.disable();
APIKeyAuthFilter filter = new APIKeyAuthFilter(principalRequestHeader);
filter.setAuthenticationManager(new AuthenticationManager() {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String principal = (String) authentication.getPrincipal();
if (!principalRequestValue.equals(principal)) {
throw new BadCredentialsException("The API key was not found or not the expected value.");
}
authentication.setAuthenticated(true);
return authentication;
}
});
}
}
}
Filter:
public class APIKeyAuthFilter extends AbstractPreAuthenticatedProcessingFilter {
private String principalRequestHeader;
public APIKeyAuthFilter(String principalRequestHeader) {
this.principalRequestHeader = principalRequestHeader;
}
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
return request.getHeader(principalRequestHeader);
}
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
return "N/A";
}
}
We are trying to user #PreAuthorize with token authentication.
When we try to use #PreAuthorize then SpringSecurity popsup with login page before an API gets called. We don't need that page as we have our own authentication process.
To skip that page we added
#SpringBootApplication( exclude = { SecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class }) on our main class.
After this the login page was skipped, but then all our API's when we trigger them gave error that Authentication needs to be there in the context.
For this we did below changes
#Configuration
public class MethodSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable().addFilter(new AuthFilter(authenticationManagerBean())).authorizeRequests().anyRequest().permitAll();
}
}
#Component
public class AuthFilter implements Filter {
private AuthenticationManager authenticationManager;
public AuthFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterchain)
throws IOException, ServletException {
final String authorizationHeader = ((HttpServletRequest) request).getHeader("Authorization");
System.out.println("===========Filter called================");
final Authentication authentication = authenticationManager
.authenticate(SecurityContextHolder.getContext().getAuthentication());
System.out.println("===========Authentication================"+authentication);
if (authentication != null && !(authentication instanceof AnonymousAuthenticationToken)
&& authentication.isAuthenticated()) {
// set authentication in security context holder
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterchain.doFilter(request, response);
}
}
Now when I'm getting error that no bean is present for AuthenticationManager.
I tried by many other ways still the bean is not getting injected in the filter
Can you comment on this ?
#Configuration
public class MethodSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable().addFilter(new AuthFilter(authenticationManagerBean())).authorizeRequests().anyRequest().permitAll();
}
}
#Component
public class AuthFilter implements Filter {
#Autowired //--->use this
private AuthenticationManager authenticationManager;
public AuthFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterchain)
throws IOException, ServletException {
final String authorizationHeader = ((HttpServletRequest) request).getHeader("Authorization");
System.out.println("===========Filter called================");
final Authentication authentication = authenticationManager
.authenticate(SecurityContextHolder.getContext().getAuthentication());
System.out.println("===========Authentication================"+authentication);
if (authentication != null && !(authentication instanceof AnonymousAuthenticationToken)
&& authentication.isAuthenticated()) {
// set authentication in security context holder
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterchain.doFilter(request, response);
}
}
try something like this it might help:
#Bean(name = BeanIds.AUTHENTICATION_MANAGER)
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
you can also refer this How To Inject AuthenticationManager using Java Configuration in a Custom Filter
Once try like this
#Configuration
public class MethodSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
AuthFilter authFilter;
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable().addFilter(authFilter).authorizeRequests().anyRequest().permitAll();//change is here
}
}
#Component
public class AuthFilter implements Filter {
private AuthenticationManager authenticationManager;
public AuthFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterchain)
throws IOException, ServletException {
final String authorizationHeader = ((HttpServletRequest) request).getHeader("Authorization");
System.out.println("===========Filter called================");
final Authentication authentication = authenticationManager
.authenticate(SecurityContextHolder.getContext().getAuthentication());
System.out.println("===========Authentication================"+authentication);
if (authentication != null && !(authentication instanceof AnonymousAuthenticationToken)
&& authentication.isAuthenticated()) {
// set authentication in security context holder
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterchain.doFilter(request, response);
}
}
Instead of creating Java object of auth filter use spring bean AuthFilter
I have created a method for persisting user details in the database and i also have a controller which is exposed at the endpoint /register. I wanted to make the /register endpoint available to all. I have used spring security and gave permit all for the /register end point.
#Configuration
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
private final UserDetailsServiceImpl userDetailsService;
#Autowired
public WebSecurityConfiguration(UserDetailsServiceImpl userDetailsService) {
this.userDetailsService = userDetailsService;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests(
request -> request.antMatchers(HttpMethod.POST,"/register").permitAll()
.anyRequest().authenticated()
);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Can someone please explain or help me out why permitAll is not working in my case. As per the code i have written the /register end point should return the user details but it returns 403. The /register endpoint is a rest endpoint which takes the user details as input and return the user details as output once the detal is persisted to the database.
#Slf4j
#RestController
public class RegistrationController {
private final UserDetailsServiceImpl userDetailsService;
#Autowired
public RegistrationController(UserDetailsServiceImpl userDetailsService) {
this.userDetailsService = userDetailsService;
}
#PostMapping(value = "/register")
public ResponseEntity<Users> registerNewUser(#Valid #RequestBody Users users) throws EmailAlreadyExistsException {
Users usersDetails = userDetailsService.processRegistration(users);
log.info("{}, Information: Successfully persisted new user",this.getClass().getSimpleName());
return new ResponseEntity<>(usersDetails,HttpStatus.OK);
}
}
I guess you are calling the url via curl or postman. You must then disable CSRF or use a GET mapping instead.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests(
request -> request.antMatchers(HttpMethod.POST,"/register").permitAll()
.anyRequest().authenticated()
);
}
We're currently writing a middleware using Spring Boot (1.2.6) to expose a REST API to our mobile/web applications. Middleware has no DB and is backed by some remote services of our customer.
For Login, we send username/password and a few more parameters (ip, user agent etc.) to remote services and get back some information about the user (name, last login, boolean change password flag etc..) including a session id. We wrote some beans to do this that we use in the corresponding controller:
#RestController
#RequestMapping(value = "/user", produces = "application/json")
public final class UserController {
#Autowired
private UserService userService;
#RequestMapping(value = "/login", method = RequestMethod.POST)
public LoginResponse login(#RequestBody final LoginRequest request, final HttpServletRequest servletRequest) {
final LoginResponse response = new LoginResponse();
final LoginServiceRequest serviceRequest = new LoginServiceRequest();
serviceRequest.setAdditionalRequestData(AdditionalRequestData.getInstance(servletRequest));
serviceRequest.setUsername(request.getUsername());
serviceRequest.setPassword(request.getPassword());
final LoginData serviceResponse = userService.login(serviceRequest);
response.setChangePassword(serviceResponse.isChangePassword());
// setting other params here...
return response;
}
}
As far as I saw, Spring Security usually depends on servlet filters which work before the controller. For instance, if I enable formLogin in configuration, it enables UsernamePasswordAuthenticationFilter which handles the authentication based on the AuthenticationManager beans I define. However I need the authentication response in this case and we send our request parameters encoded in JSON. So it seems that filters don't work for us.
Instead, I created an AuthenticationProvider and AuthenticationToken and changed above code to something like this:
#RestController
#RequestMapping(value = "/user", produces = "application/json")
public final class UserController {
#Autowired
private AuthenticationManager auth;
#Autowired
private UserService userService;
#RequestMapping(value = "/login", method = RequestMethod.POST)
public LoginResponse login(#RequestBody final LoginRequest request,
final HttpServletRequest servletRequest) throws ServletException {
final LoginResponse response = new LoginResponse();
final Authentication authenticationToken = new CustomAuthenticationToken(
request.getUserId(),
request.getPassword(),
AdditionalRequestData.getInstance(servletRequest)
);
final LoginData loginData =
((CustomAuthenticationToken) auth.authenticate(authenticationToken)).getLoginData();
response.setChangePassword(loginData.isChangePassword());
// setting other params here...
return response;
}
}
The AuthenticationProvider is responsible for calling the userService.login method as well as setting the AuthenticationToken into SecurityContext.
This is our security configuration:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
#Autowired
private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
#Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
// #formatter:off
http
.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and()
.authorizeRequests()
.antMatchers("/version/**").permitAll()
.anyRequest().hasAnyRole(Constants.ROLE_USER);
// #formatter:on
}
}
This manuel approach actually works. We also make use of authorities (ROLE_USER etc..) for granting access to different endpoints.
Is there a better solution to this? Do you think we lose some features of Spring Security when we do this?
I am using third party authenticator, which is invoked when the application starts and shows login screen and after successful authentication, grants access to the application.
After successful authentication it forwards to a spring controller for user loading.
I would like to know how can i use spring security api for custom providers.
I want to call spring security api after the authentication is done and while the user details are being loaded etc to build up roles and GrantedAuthority etcs.
Controller
#Controller
public class WelcomeController {
#RequestMapping(value = { "/welcome" }, method = RequestMethod.POST)
public ModelAndView defaultPage() {
ModelAndView model = new ModelAndView();
//... load user profile from third party authenticator.
return model;
}
}
Security Config
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private MyCustomAuthenticationProvider thirdPartyAuthenticationProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/admin/**")
.access("hasRole('ROLE_ADMIN')")
.and().exceptionHandling().accessDeniedPage("/error.html");
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(thirdPartyAuthenticationProvider);
}
}
Custom Provider
public class MyCustomAuthenticationProvider implements AuthenticationProvider{
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// use the credentials to try to authenticate against the third party system
if (authenticatedAgainstThirdPartySystem()) {
List<GrantedAuthority> grantedAuths = new ArrayList<>();
return new UsernamePasswordAuthenticationToken(name, password, grantedAuths);
} else {
throw new AuthenticationException("Unable to auth against third party systems");
}
}
#Override
public boolean supports(Class<?> authentication) {
/return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
Any Suggestions?