Spring Boot Security , Form based login to invoke a custom API for authentication - spring

So wondering if it's possible or not. I'm trying to authenticate my rest API's in spring boot with a post API which is already present which validate the user.
I'm using fromLogin based authentication and trying to invoke that Rest Controller and use that login API by passing the post parameter. There I'm creating the spring security context
I'm trying to invoke the POST API of login on login submit. Authentication is not working.
Can we achieve this using form login? Let me know if my understanding very new to spring boot security
My code somewhat looks like this
Controller
#RestController
#RequestMapping("/somemapping")
public class AuthController {
#RequestMapping(value = "/login", method = RequestMethod.POST)
public UserData authenticateUser(#RequestBody(required = true) UserInfo userInfo, HttpServletRequest request) {
// get user data goes here
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userdata.getUsername(), userdata.getPassword(), new ArrayList < > ());
authentication.setDetails(userdata);
SecurityContextHolder.getContext().setAuthentication(authentication);
send the info to caller
return userdata;
}
//Security Adapter
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/somemapping/**", "/login*", ).permitAll()
.anyRequest().authenticated().and()
.formLogin().loginPage("/")
.and().authenticationEntryPoint(authenticationPoint);
}

Related

Spring oauth2login oidc grant access based on user info

I'm trying to set up Authentication based on this tutorial: https://www.baeldung.com/spring-security-openid-connect part 7 specifically.
I have filled properties and configured filter chain like this:
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests -> authorizeRequests
.anyRequest().authenticated())
.oauth2Login(oauthLogin -> oauthLogin.permitAll());
return http.build();
}
which works, but now all users from oidc can connect log in. I want to restrict access based on userinfo. E.g. add some logic like:
if(principal.getName() == "admin") {
//allow authentication
}
are there any way to do it?
I tried to create customer provider like suggested here: Add Custom AuthenticationProvider to Spring Boot + oauth +oidc
but it fails with exception and says that principal is null.
You can retrieve user info when authentication is successful and do further checks based user info.
Here is sample code that clears security context and redirects the request:
#Component
public class OAuth2AuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
if(authentication instanceof OAuth2AuthenticationToken) {
OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication;
// OidcUser or OAuth2User
// OidcUser user = (OidcUser) token.getPrincipal();
OAuth2User user = token.getPrincipal();
if(!user.getName().equals("admin")) {
SecurityContextHolder.getContext().setAuthentication(null);
SecurityContextHolder.clearContext();
redirectStrategy.sendRedirect(request, response, "login or error page url");
}
}
}
}
Are you sure that what you want to secure does not include #RestController or #Controller with #ResponseBody? If so, the client configuration you are referring to is not adapted: you need to setup resource-server configuration for this endpoints.
I wrote a tutorial to write apps with two filter-chains: one for resource-server and an other one for client endpoints.
The complete set of tutorials the one linked above belongs to explains how to achieve advanced access-control on resource-server. Thanks to the userAuthoritiesMapper configured in resource-server_with_ui, you can write the same security expressions based on roles on client controller methods as I do on resource-server ones.

How do I get the right user principal with Spring-Boot and Oauth2?

I am practising Spring-Boot 2.2 + Spring Security 5 + Oauth2. Following a lot of examples I am hitting a wall here.
My spring security configuration is this:
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/css/**", "/webjars/**").permitAll()
.anyRequest().authenticated()
.and().oauth2Login().userInfoEndpoint()
.userService(new DefaultOAuth2UserService());
}
and I have a controller with this method:
#GetMapping("/create")
public ModelAndView create(Principal principal) {
log.debug(principal);
return new ModelAndView("create.html", "topicForm", new TopicForm());
}
in the Thymeleaf template I would call <span sec:authentication="name">User</span>, and it only returns a number.
in debug, authentication is org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken, and the Principal is a DefaultOidcUser, the name attribute is "sub", which is not a name but a number in google's oauth response.
DefaultOAuth2UserService is never called before my breakpoint hits in the controller.
Where did I take the wrong turn?
--edit--
In further debugging, I think the problem stems from OAuth2LoginAuthenticationFilter calling org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService which would be configurable by oidcUserService(oidcUserService())
To get current principal you can use #AuthenticationPrincipal annotation which resolves Authentication.getPrincipal() so you can add it as argument.
public ModelAndView create(#AuthenticationPrincipal Principal principal) {
log.debug(principal);
return new ModelAndView("create.html", "topicForm", new TopicForm());
}
You can make use of the SecurityContextHolder.
public ModelAndView create() {
Object principal = SecurityContextHolder.getContext().getAuthentcation().getPrincipal();
log.debug(principal);
return new ModelAndView("create.html", "topicForm", new TopicForm());
}

Spring boot basic authentication with token for a RESTAPI

I need to provide user login with SpringBoot application.
User login request will be a Rest request having payload comprise of "username" and "password".
I need to validate those credentials first time from DB and generate a token having validity for specific time.
Then after login all the subsequent requests will have that token, and that token will be verified each time.
I have done the token verification part but I am really confused about first time login, I have no clue how to do it.
Even on first time login request, system is going to check for token authentication which obviously getting failed.
I want system to simply generate token on first time after validating name and password from db.
This is the first time I am implementing User login with Spring Boot Security, so I am pretty clueless about it. Although I have researched and read a lot online but still not able to figure out this part.
EDIT:
Following is the security config class which extends WebSecurityConfigurerAdapter
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(getPasswordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable();
http.authorizeRequests()
.antMatchers("/","**/firstPage").authenticated()
.anyRequest().permitAll()
.and()
.formLogin().loginPage("/login").
permitAll()
.and().logout().permitAll();
}
Following is the request that will be called after login.How to authenticate user in it using the token already generated? Token is being sent in Header of the request.
#PostMapping(value = "/home")
public ResponseEntity<ConsolidateResponse> TestReques(#RequestBody TestParam testParam)
throws Exception {
//Some logic
}
If you disable form login from spring security configuration class and expose one rest endpoint (/auth) you can handle login and generate token.Here i used jwt for token generation.
#RequestMapping(value = "/auth", method = RequestMethod.POST)
public ResponseEntity<?> createAuthenticationToken(#RequestBody JwtAuthenticationRequest authenticationRequest) throws AuthenticationException, IOException {
// Perform the security
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
authenticationRequest.getUsername(), authenticationRequest.getPassword());
final Authentication authentication = authManager.authenticate(token);
if (!authentication.isAuthenticated()) {
throw new BadCredentialsException("Unknown username or password");
}
// Reload password post-security so we can generate token
final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
final String jwtoken = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(responseBean);
}
When use stateless authentication we can pass token parameter explicitly to controller and validate it.In case session based authentication is on we can also use #AuthenticationPrincipal for to retrieve current logged in user.
//Stateless authentication
#PostMapping(value = "/home")
public ResponseEntity<ConsolidateResponse> test(#RequestBody TestParam testParam,String token)
throws Exception {
Boolean isValidToken = jwtTokenUtil.validateToken(token);
if(isValidToken) {
//Some logic
}else {
//invalid request
}
}
#PostMapping(value = "/home")
public ResponseEntity<ConsolidateResponse> test(#RequestBody TestBean requestToken,
#AuthenticationPrincipal User contextPrincipal, HttpServletRequest req) {
Optional.ofNullable(contextPrincipal).orElseThrow(InvalidUserSession::new);
//some logic
}

Spring #RestController single method for anonymous and authorized users

I have a following Spring RestController:
#RestController
#RequestMapping("/v1.0/tenants")
public class TenantController {
#Autowired
private TenantService tenantService;
#RequestMapping(value = "/{tenantId}", method = RequestMethod.GET)
public TenantResponse findTenantById(#PathVariable #NotNull #DecimalMin("0") Long tenantId) {
Tenant tenant = tenantService.findTenantById(tenantId);
return new TenantResponse(tenant);
}
}
findTenantById method should be accessed by anonymous and authorized users. In case of anonymous user SecurityContextHolder.getContext().getAuthentication() must return NULL or AnonymousAuthenticationToken but in case of authorized - Authentication object.
In my application I have implemented security model with OAuth2 + JWT tokens.
This my config:
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.antMatcher("/v1.0/**").authorizeRequests()
.antMatchers("/v1.0/tenants/**").permitAll()
.anyRequest().authenticated()
.and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(STATELESS);
// #formatter:on
}
Also, for secure endpoints I'm applying #PreAuthorize annotation where needed but not in case of findTenantById because as I said previously, I need to grant access to this endpoint for anonymous and authorized users. Inside of endpoint business logic I'll decide who will be able to proceed based on different conditions.
Right now even I have provided my accessToken for this endpoint I can't get an authenticated User object from SecurityContextHolder.getContext().getAuthentication().
How to configure this endpoint in order to be working in a way described above ?
I think I have found a solution - I have annotated my method with:
#PreAuthorize("isAnonymous() or isFullyAuthenticated()")
Please let me know if there is any better solutions.

Spring OAuth2 Client Credentials with UI

I'm in the process of breaking apart a monolith into microservices. The new microservices are being written with Spring using Spring Security and OAuth2. The monolith uses its own custom security that is not spring security, and for now the users will still be logging into the monolith using this homegrown security. The idea is that the new MS apps will have their own user base, and the monolith app itself will be a "user" of these Services. I've successfully set up an OAuth2 Auth Server to get this working and I'm able to log in with Client Credentials to access the REST APIs.
The problem is that the Microservices also include their own UIs which will need to be accessed both directly by admins (using the new Microservice users and a login page) and through the monolith (hopefully using client credentials so that the monolith users do not have to log in a second time). I have the first of these working, I can access the new UIs, I hit the login page on the OAuth server, and then I'm redirected back to the new UIs and authenticated & authorized.
My expectation from the is that I can log in to the OAuth server with the client credentials behind the scenes and then use the auth token to have the front end users already authenticated on the front end.
My question is - what should I be looking at to implement to get the client credentials login to bypass the login page when coming in through the UI? Using Postman, I've gone to http://myauthapp/oauth/token with the credentials and gotten an access token. Then, I thought I could perhaps just GET the protected UI url (http://mymicroservice/ui) with the header "Authorization: Bearer " and I was still redirected to the login page.
On the UI app:
#Configuration
#EnableOAuth2Client
protected static class ResourceConfiguration {
#Bean
public OAuth2ProtectedResourceDetails secure() {
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
details.setId("secure/ui");
details.setClientId("acme");
details.setClientSecret("acmesecret");
details.setAccessTokenUri("http://myoauthserver/secure/oauth/token");
details.setUserAuthorizationUri("http://myoauthserver/secure/oauth/authorize");
details.setScope(Arrays.asList("read", "write"));
details.setAuthenticationScheme(AuthenticationScheme.query);
details.setClientAuthenticationScheme(AuthenticationScheme.form);
return details;
}
#Bean
public OAuth2RestTemplate secureRestTemplate(OAuth2ClientContext clientContext) {
OAuth2RestTemplate template = new OAuth2RestTemplate(secure(), clientContext);
AccessTokenProvider accessTokenProvider = new AccessTokenProviderChain(
Arrays.<AccessTokenProvider> asList(
new AuthorizationCodeAccessTokenProvider(),
new ResourceOwnerPasswordAccessTokenProvider(),
new ClientCredentialsAccessTokenProvider())
);
template.setAccessTokenProvider(accessTokenProvider);
return template;
}
}
SecurityConfig:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private OAuth2ClientContextFilter oAuth2ClientContextFilter;
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.anonymous().disable()
.csrf().disable()
.authorizeRequests()
.antMatchers("/ui").hasRole("USER")
.and()
.httpBasic()
.authenticationEntryPoint(oauth2AuthenticationEntryPoint());
}
private LoginUrlAuthenticationEntryPoint oauth2AuthenticationEntryPoint() {
return new LoginUrlAuthenticationEntryPoint("/login");
}
}

Resources