Method Based Authorization at Spring Boot - java

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.

Related

Map OAuth2 user attributes to Object prior to controller method call

What I have currently
I'm currently implementing an OIDC Resource Provider for my company. They use their intern OIDC servers, which I managed to work with by following this example: https://github.com/jgrandja/oauth2login-demo/tree/linkedin
I'm now able to retrieve user information from the Authorization Server, like that:
#RestController
#RequestMapping("/some/route")
public class SomeController {
#GetMapping("/some/route")
public ResponseEntity<?> getSomething(#RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient authorizedClient) {
String userInfoEndpointUri = authorizedClient.getClientRegistration()
.getProviderDetails().getUserInfoEndpoint().getUri();
Map userAttributes = this.webClient
.get()
.uri(userInfoEndpointUri)
.attributes(oauth2AuthorizedClient(authorizedClient))
.retrieve()
.bodyToMono(Map.class)
.block();
String firstName = (String) userAttributes.get("first_name");
String lastName = (String) userAttributes.get("last_name");
...
}
}
What I'd like
I am now searching for a solution to map the userAttributes to an Object prior to
the controller method call, so that I get e.g.:
#GetMapping("/some/route")
public ResponseEntity<?> getSomething(MyCostumUserBean user) {
String firstName = user.getFirstName();
String lastName = user.getLastName();
...
}
I read something about the ChannelInterceptor and HandlerInterceptor and also the PrincipalExtractor and AuthoritiesExtractor.
The problem is, that I am just learning the Spring framework and these possibilities are overwhelming me.
It would be a plus if that method would allow some validation and would immediately respond with Error codes if the validation fails.
After that is achieved, I would like to add additional information to MyCostumUserBean from another server, which I send the identity of the current session's user to and receive e.g. Role/Permissions of that user.
I tried to put it in a picture:
Question
What is the proper / by the Spring Framework intended way to deal with that? How do I achieve that?
Extra: Is it secure to rely on OAuth2AuthorizedClient.getPrincipalName()? Or can that be faked by an user, by faking the Cookie/Token?
I think you are asking the way to configure the success handler, or a filter which can check the user attributes.
If this is what you are asking, There are many ways to do it.
For examples:
Use User scope check:(need to assign the scope to the user in advance.)
#ResponseBody
#GetMapping("/some/route")
public String getSomeThing(#RegisteredOAuth2AuthorizedClient("custom") OAuth2AuthorizedClient authorizedClient) {
Set<String> scopes = authorizedClient.getAccessToken()
.getScopes();
if (scopes.contains("users:read")) {
} else if (scopes.contains("users:read")) {
return " page 1";
} else {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Forbidden.");
}
}
You can put some logic in the successHandler:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/**")
.permitAll().and()
.formLogin()
.successHandler(successHandler());
}
#Bean
public CustomSuccessHandler successHandler() {
return new CustomSuccessHandler();
}
If you want to apply a filter for your Security Chians:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
...
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
...
.and()
.addFilterBefore(getBeforeAuthenticationFilter(), CustomBeforeAuthenticationFilter.class)
.formLogin()
.loginPage()
.permitAll()
...
}
public UsernamePasswordAuthenticationFilter getBeforeAuthenticationFilter() throws Exception {
CustomBeforeAuthenticationFilter filter = new CustomBeforeAuthenticationFilter();
....
return filter;
}
}
You can also achieve the same purpose by using a Customizing Filter Chains, by give the different order and the relative login in it.
#Configuration
#Order(SecurityProperties.BASIC_AUTH_ORDER - 10)
public class ApplicationConfigurerAdapterForUserGroup1 extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
...;
}
}
#Configuration
#Order(SecurityProperties.BASIC_AUTH_ORDER - 10)
public class ApplicationConfigurerAdapterForUserGroup2 extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
...;
}
}

Spring Security Limiting URL access by roles

I am trying to implement SpringSecurity mechanism on this little project, that will limit interactions with the URL of the request by roles.
I have two roles USER and ADMIN, USER can see the items, but not add or delete them, while ADMIN can do both.
Now the problem, the requests from USER role and even unauthenticated users to create/delete/read an item are allowed somehow. It seems to me that my application is not configured correctly somewhere.
SecurityConfig:
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("{noop}12345").roles("USER").and()
.withUser("admin").password("{noop}12345").roles("ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and().authorizeRequests()
.antMatchers("api/**").hasRole("ADMIN")
.antMatchers("api/items", "api/items/").hasRole("USER")
.anyRequest().authenticated()
.and().csrf().disable().headers().frameOptions().disable();
}
}
Controller:
#RestController
public class ItemController {
#Autowired
private ItemService itemService;
#GetMapping("/api/items")
public List<Item> getItems() {
return itemService.getAllItems();
}
#PostMapping(value = "/api/addItem",consumes = {"application/json"},produces = {"application/json"})
#ResponseBody
public Item addItem(#RequestBody Item item) {
itemService.addItem(item);
return item;
}
#DeleteMapping(value = "api/deleteItem/{id}")
#ResponseBody
public String deleteItem(#PathVariable int id) {
itemService.deleteItem(id);
return "Deleted";
}
}
I am sending requests to the following URL's:
http://localhost:8080/api/items // GET
http://localhost:8080/api/addItem // POST
http://localhost:8080/api/deleteItem/4 // DELETE
Have you tried adding slashes to your antmatcher patterns, such as:
antMatchers("/api/**").hasRole("ADMIN")
The Spring documentation mentions:
Note: a pattern and a path must both be absolute or must both be relative in order for the two to match. Therefore it is recommended that users of this implementation to sanitize patterns in order to prefix them with "/" as it makes sense in the context in which they're used.
Furthermore, Spring security uses the first match of all the matching rules expressed. I would recommend reordering the matchers from most-specific to less-specific as otherwise a call to api/items will be matched by the api/** matcher instead of being matched by the api/items matcher.
.antMatchers("/api/items", "api/items/").hasRole("USER")
.antMatchers("/api/**").hasRole("ADMIN")
On top of #GetMapping or #PostMapping you can add following annotation to manage Role based access
#PreAuthorize("hasAnyRole('ROLE_ADMIN')")
#PostMapping(value = "/api/addItem",consumes = {"application/json"},produces = {"application/json"})
#ResponseBody
public Item addItem(#RequestBody Item item) {
itemService.addItem(item);
return item;
}

Accessing secure resource with spring resource

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

Integrating Spring Security into Spring Web Application (JSON REST API)

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?

How to unsecure a method with Spring security

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();
}

Categories

Resources