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?
Related
I am trying to integrate spring security into my application and for some reason (unknown to me) I keep on getting a 403 error on every request. I am convinced it has something to do with spring security. Below is a snippet of my code for further details.
This is my first attempt of integrating spring security to my application so I could be missing something.
I have this in my WebSecurity class
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserService userService;
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
private JwtTokenProvider jwtTokenProvider;
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().disable()
.csrf().disable()
.cors().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers( HttpMethod.POST, "/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.apply(new JwtConfigurer(jwtTokenProvider));
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
And in my controller I have this
#RestController
#RequestMapping("/auth")
public class LoginController {
#Autowired
private UserService userService;
#RequestMapping(value = "/register", method = RequestMethod.POST, produces = "application/json", consumes = "application/json")
#ResponseBody
public Response<UserDto> register(#RequestBody Request<UserDto> request){
MessengerUser user = userService.saveUser(request.getData());
return new Response<>(new ModelMapper().map(user, UserDto.class));
}
#RequestMapping(value = "/sign-in", method = RequestMethod.POST, produces = "application/json", consumes = "application/json")
#ResponseBody
public Response<LoginDto> signin(#RequestBody Request<UserDto> request) {
LoginDto loginDto = userService.authenticateUser(request.getData());
return new Response<>(loginDto);
}
}
With my Request
{
"data":{
"username": "username",
"password": "password"
}
}
My suspicion is that it has something to do with my configuration but I'm not sure what else to try.
As it turns out, it was my mistake. I had the wrong request type, Silly mistake on my part.
Everything about was right but the request type was GET instead of POST
I have to methods published as rest services.
I want to apply basic authorization security on one method lest say "gpnfeedback".
I do not want to apply any authorization over sendgpn
How I can configure SecurityConfig.java? I have used following configration but still having authorzation error when callling http://localhost:7071/gpns/rest/sendgpn
Controller
#Controller
#RequestMapping("/gpns/rest/")
public class GpnsRestController {
#CrossOrigin
#RequestMapping(value = "/sendgpn", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE, consumes = { MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_VALUE })
public #ResponseBody
GpnsResponse sendgpn(#Valid #RequestPart(value = "data", required = true) SendGpnMessageMsisdnListReq sendGpnMessageMsisdnListReq, #Valid #ModelAttribute(value = "photo") MultipartFile photo, #Valid #ModelAttribute(value = "video") MultipartFile video,
#Valid #ModelAttribute(value = "videothumbnail") MultipartFile videothumbnail) {
}
#RequestMapping(method = RequestMethod.POST, value = "/gpnfeedback", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody
GpnsResponse gpnfeedback(HttpServletRequest http, #Valid #RequestBody GpnFeedbackReq gpnFeedbackReq) {
}
}
Security
#Configuration
#EnableWebSecurity(debug = true)
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
public static final String ROLE_CLIENT = "CLIENT_USER";
#Autowired
private DatabaseAuthenticationProvider databaseAuthenticationProvider;
#Autowired
private GpnBasicAuthenticationEntryPoint basicAuthenticationEntryPoint;
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/soap/lb/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.httpBasic().authenticationEntryPoint(this.basicAuthenticationEntryPoint);
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// #formatter:off
http.authorizeRequests()
.antMatchers("/gpns/rest/gpnfeedback/**").hasRole(ROLE_CLIENT)
.anyRequest().authenticated().and().httpBasic();
// #formatter:on
}
#Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
//will be invoked in given order
builder.authenticationProvider(this.databaseAuthenticationProvider);
}
}
UPDATE-1:
I have changed the rules with following one. Althout I can send http://localhost:7071/gpns/rest/sendgpn method without any authorization, http://localhost:7071/gpns/rest/gpnfeedback is not hanled by databaseAuthenticationProvider
http.authorizeRequests()
.antMatchers("/gpns/rest/gpnfeedback/**").hasRole(ROLE_CLIENT)
.antMatchers("/gpns/rest/sendgpn/**").permitAll()
.anyRequest().authenticated().and().httpBasic();
I think your issue is related to this line in your configuration:
.anyRequest().authenticated().and().httpBasic();
Basically, what you're saying here is that every request (aside from the ignored on) has to be authenticated but you don't care about what roles it has. Try using this one instead:
.anyRequest().permitAll().and().httpBasic()
Alternatively, if you wish only to permit sendgpn, you could use this:
http.authorizeRequests()
.antMatchers("/gpns/rest/gpnfeedback/**").hasRole(ROLE_CLIENT)
.antMatchers("/gpns/rest/sendgpn/**").permitAll()
.anyRequest().authenticated().and().httpBasic();
EDIT
As for your update, my guess is that you've somehow misconfigured the provided or you have incorrect data in your DB. For instance if ROLE_CLIENT has the value of "CLIENT" then Spring will expect the value in DB to be "ROLE_CLIENT" - it adds the "ROLE_" prefix to roles.
I have this SpringSecurity config
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsServiceImpl detailsService;
#Autowired
public void registerGlobalAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(detailsService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/index").access("hasRole('USER')");
http.userDetailsService(detailsService);
}
}
when I open index.html page I get 403 error. It is because user is anonymous. But I want check user and set role and detail before opened this page. For this i write this service
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private HttpServletRequest request;
#Autowired
AuthService authService;
#Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
String ipAddress = request.getRemoteAddr();
AuthLkUser authLkUserByIp = authService.getAuthLkUserByIp(ipAddress);
if (authLkUserByIp == null) return null;
boolean b = authService.checkAuthLkUser(authLkUserByIp);
if (b) return null;
Set<GrantedAuthority> roles = new HashSet();
roles.add(new SimpleGrantedAuthority("USER"));
UserDetails userDetails = new org.springframework.security.core.userdetails.User(authLkUserByIp.getMsisdn(),
authLkUserByIp.getSubsId(), roles);
return userDetails;
}
}
But this service is never called.
In Spring Boot if you have a custom Spring Security Configuration then it will not use Auto Configuration for Spring Security.
In your Spring security configuration you haven't given anyway to authenticate a user. There are form based, basic header based, etc authentication available in Spring Security. You should use one of that to authenticate user so that during authentication your UserDetailsServiceImpl will be used to load the user.
Follow this post for form based login
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);
}
}
I have implemented Spring Security for a RESTful web service project. It has Request Mappings with same url patterns but with different Request Method types.
#RequestMapping(value = "/charity/accounts", method = RequestMethod.POST)
public AccountResponseDto createAccount(HttpServletResponse response, #RequestBody AccountRequestDto requestDTO) {
// some logics here
}
#RequestMapping(value = "/charity/accounts", method = RequestMethod.GET)
public AccountResponseDto getAccount(HttpServletResponse response) {
// some logics here
}
#RequestMapping(value = "/charity/accounts", method = RequestMethod.PUT)
public void updateAccount(HttpServletResponse response, #RequestBody AccountRequestDto requestDTO){
// some logics here
}
Currently all of these methods require Authorization to execute, but I need to remove authorization for createAccount(...) method. Are there annotation based solutions?
Note: I need a solution that will not effect to do changes for url patterns, as it will impact in many other modules.
That's why we have roles,authorizations..first we can define who can GET/PUT/POST and grant authorities to the user accordingly.
Then we can annotate as #Secured("ROLE_ADMIN") on GET/PUT/POST methods accordingly.
To unsecure GET, you can add #PreAuthorize("isAnonymous()") or #Secured("MY_CUSTOM_ANONYM_ROLE")
Below is a sample configuration which would permit requests for signup and about:
#EnableWebSecurity
#Configuration
public class CustomWebSecurityConfigurerAdapter extends
WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
auth
.inMemoryAuthentication()
.withUser("user") // #1
.password("password")
.roles("USER")
.and()
.withUser("admin") // #2
.password("password")
.roles("ADMIN","USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeUrls()
.antMatchers("/signup","/about").permitAll();
}
}
You can refer Spring Security Java Config for detailed information.
A suggestion on your Controller. If all requests prefixed with /charity to be handled by CharityController, you can map requests in the below way:
#Controller
#RequestMapping(value="/charity")
class CharityController {
#RequestMapping(value = "/accounts", method = RequestMethod.GET)
public AccountResponseDto getAccount(HttpServletResponse response){
}
}
Update
The following should work for you.
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(HttpMethod.POST, new String [] {"/charity/accounts", "/charity/people"}).permitAll();
}