How to unsecure a method with Spring security - java

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

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 allow each user to see their own profile but none else

In Spring MVC with Spring Security, is it possible to achieve this?
#Override WebSecurityConfigurerAdapter.configure(HttpSecurity)
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.authorizeRequests()
.mvcMatchers("/users/{authentication.principal.username}").hasAnyRole(ADMIN, MANAGER)
.antMatchers("/users/**").hasRole(ADMIN)
.anyRequest().authorized()
...
}
/users/** is a restricted area and should be accessible by admins only. But managers should still be able to see their own profile (/users/user_with_manager_role), and only their own profile, not those of any other users (regardless of their role).
Solution
I've found a solution in Andrew's answer. My Code now looks like this:
WebSecurityConfigurerAdapter
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true) // added this annotation
public class SecurityConfig extends WebSecurityConfigurerAdapter
#Override WebSecurityConfigurerAdapter.configure(HttpSecurity)
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.authorizeRequests()
// removed /users handling
.anyRequest().authorized()
...
}
UsersController
#Controller
#RequestMapping("/users")
public class UsersController
{
#GetMapping("{username}")
#PreAuthorize("authentication.principal.username == #username) || hasRole('ADMIN')")
public String usersGet(#PathVariable("username") String username)
{
// do something with username, for example get a User object from a JPA repository
return "user";
}
}
I'm afraid it's not possible: when this configuration is being set up, it has no info about {authentication.principal.username} which will be resolved at some point in future.
But Spring gives you a bunch of built-in method security expressions you can annotate your methods with.
Starting from a simple expression like #PreAuthorize("hasRole('ADMIN')"), you might end up with a custom one:
#XMapping(path = "/users/{username}")
#PreAuthorize("#yourSecurityService.isMyPage(authentication.principal, #username)")
public void yourControllerMethod(#PathVariable String username);
#yourSecurityService.isMyPage(authentication.principal, #username) refers to your #Service method public boolean isMyPage(Principal, String).
How about something like this:
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/myself").hasAnyRole(ADMIN, MANAGER)
.antMatchers("/users/**").hasRole(ADMIN)
.anyRequest().hasAnyRole(ADMIN, MANAGER)
...
}
#RequestMapping(value = "/myself", method = RequestMethod.GET)
public Profile getMyself() {
// return the profile of the loged in user
}
With this manager and admins can get their own profile and admins can also request other profiles with /users/{username}.

Method Based Authorization at Spring Boot

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.

Spring Security is not recognizing user roles

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

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?

Categories

Resources