How to set Manually Authenticate User with Spring boot Security - spring

I Created Spring boot application with SSO login. I have used saml.xml file for it. After SSO login i called getAuthentication() method it will return annonymousUser
every time. I want to get SSO Logged User details.
Principal principal =
SecurityContextHolder.getContext().getAuthentication();
Security configuration class look like following :
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/actuator").authenticated();
http.headers().cacheControl().disable();
http.csrf().disable();
http.logout().logoutSuccessUrl("/assets/logout.html");
}
}

You can make user login into spring security like below.
public void login(HttpServletRequest req, String user, String pass) {
UsernamePasswordAuthenticationToken authReq
= new UsernamePasswordAuthenticationToken(user, pass);
Authentication auth = authManager.authenticate(authReq);
SecurityContext sc = SecurityContextHolder.getContext();
sc.setAuthentication(auth);
HttpSession session = req.getSession(true);
session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, sc);
}
Refer manually-set-user-authentication-spring-security

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.

Spring Boot OAuth2 password login via basic auth

I have a Spring Boot application with multiple http security configurations. Each of them is using external Keycloak.
API URLs are using Bearer token authentication
swagger URLs are using authentication code flow (user interaction needed)
URLs that authenticates via Basic Auth
First 2 works fine but I can't get basic auth configuration running. For that I would like to use OAuth2 grant type password.
My application.properties oauth2 configuration:
spring.security.oauth2.client.registration.keycloak2.client-id=${KEYCLOAK_RESOURCE}
spring.security.oauth2.client.registration.keycloak2.client-secret=${KEYCLOAK_RESOURCE_CLIENT_SECRET}
spring.security.oauth2.client.registration.keycloak2.authorization-grant-type=password
spring.security.oauth2.client.registration.keycloak2.scope=openid
spring.security.oauth2.client.provider.keycloak2.issuer-uri=${keycloak.auth-server-url}/realms/${keycloak.realm}
My configuration for Basic auth endpoints looks like this:
#Configuration
#Order(2)
public static class ProcessConfigurationAdapter extends WebSecurityConfigurerAdapter {
public static class OAuth2PasswordAuthenticationProvider implements AuthenticationProvider {
private final OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient;
private final OAuth2UserService<OAuth2UserRequest, OAuth2User> userService;
private final ClientRegistrationRepository clientRegistrationRepository;
private GrantedAuthoritiesMapper authoritiesMapper = ((authorities) -> authorities);
public OAuth2PasswordAuthenticationProvider(
OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient,
OAuth2UserService<OAuth2UserRequest, OAuth2User> userService,
ClientRegistrationRepository clientRegistrationRepository) {
super();
this.accessTokenResponseClient = accessTokenResponseClient;
this.userService = userService;
this.clientRegistrationRepository = clientRegistrationRepository;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (!(authentication instanceof UsernamePasswordAuthenticationToken)) {
return null;
}
final UsernamePasswordAuthenticationToken usernamePassword = (UsernamePasswordAuthenticationToken) authentication;
final String username = (String) usernamePassword.getPrincipal();
final String password = (String) usernamePassword.getCredentials();
final String registrationId = "keycloak2";
final ClientRegistration keycloak2 = clientRegistrationRepository.findByRegistrationId(registrationId);
final OAuth2PasswordGrantRequest request = new OAuth2PasswordGrantRequest(keycloak2, username, password);
final OAuth2AccessTokenResponse accessTokenResponse = accessTokenResponseClient.getTokenResponse(request);
final OAuth2User oauth2User = this.userService.loadUser(new OAuth2UserRequest(
keycloak2, accessTokenResponse.getAccessToken(), accessTokenResponse.getAdditionalParameters()));
final Collection<? extends GrantedAuthority> mappedAuthorities = this.authoritiesMapper
.mapAuthorities(oauth2User.getAuthorities());
final OAuth2AuthenticationToken authenticationResult = new OAuth2AuthenticationToken(oauth2User, mappedAuthorities, registrationId);
return authenticationResult;
}
#Override
public boolean supports(Class authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/v1/process/**")
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.oauth2Client()
.and()
.httpBasic();
http.csrf().disable();
}
#Bean
public OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient() {
return new DefaultPasswordTokenResponseClient();
}
#Bean
public OAuth2PasswordAuthenticationProvider oAuth2PasswordAuthenticationProvider(
OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient,
OAuth2UserService<OAuth2UserRequest, OAuth2User> userService,
ClientRegistrationRepository clientRegistrationRepository) {
// Here I'm missing userService
return new OAuth2PasswordAuthenticationProvider(accessTokenResponseClient, userService, clientRegistrationRepository);
}
}
I've got Parameter 1 of method oAuth2PasswordAuthenticationProvider in com.example.config.SecurityConfig$ProcessConfigurationAdapter required a bean of type 'org.springframework.security.oauth2.client.userinfo.OAuth2UserService' that could not be found.
I thought it would autowire based on configuration in application.properties but no. How can I obtain it?
Password grant flow is deprecated. Don't try to use it.
Authorization-code flow is a protocol between client and authorization-server to authenticate users and acquire access-token for client to act on behalf of those users. It is to be used client side (Angular, React, Vue, Flutter, etc. or Spring modules with Thymeleaf or other sever-side rendering) and has nothing to do with REST API.
To authenticated trusted programs (server-side applications that you can trust to keep a secret actually secret), you should use client-credentials flow to acquire access-tokens (for the client itself, not on behalf of the user). If you write such Spring services, configure it as OAuth2 client with client credentials.
In both cases from the resource-server point of view (the Spring REST API documented with Swagger), this doesn't make a difference: requests come with an Authorization header containing a Bearer access-token, and this is what you should build security-context from. Sample there.

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
}

How to validate user roles from request header with spring security configuration

I have added spring security with taglibs for role based content loading in my application . The authentication process will be taken care by external service and after successful authentication , the external service will add user detail and role detail in the request header. In my application I have to get the roles from request header and need to validate the roles with spring security configure method.
Help me on how to validate the roles .
Spring security Class:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers("/console")
.access("hasRole('MASTER ADMIN') or hasRole('ADMIN') or hasRole('DEV') or hasRole('QA')")
.and()
.formLogin()
.defaultSuccessUrl("/console", true)
.and().logout().logoutSuccessUrl("/login");
}
}
Controller Class:
#RequestMapping(value = "/login")
public ModelAndView validateLogin(final HttpServletRequest request, final HttpServletResponse response) {
final ModelAndView modelView = new ModelAndView();
final String loginUserId = request.getParameter("USER");
final String companyCode = request.getHeader("ROLE");
final String firstName = request.getHeader("FIRSTNAME");
final String lastName = request.getHeader("LASTNAME");
//** some code to validate the role details with spring security configure method ie (has access method) and return the default success url based on the role.

How to redirect UsernameNotFoundException from PreAuthenticatedAuthenticationProvider when using multiple AuthenticationProviders?

Using Spring Security 4.02, can anyone help with some tips on how I can handle UsernameNotFoundException from PreAuthenticatedAuthenticationProvider when using multiple AuthenticationProviders so that authenticated requests, with the correct header, but which are unauthorized, are sent to a specific URL instead of the forms-login page?
Let me explain further what I'm trying to accomplish for accessing a web app being secured by SSO behind a proxy. Not all users who are authenticated by SSO will have access to this app. So I need to account for 3 access scenarios:
authenticated user (header is present) is authorized (username/roles are present in app's db)
authenticated user (header is present) is unauthorized (username/roles are not present in app's db)
unauthenticated user with username/roles present in app's db
The actions when accessing the website should be:
authenticated/authorized user proceeds directly to target URL
authenticated/unauthorized user is redirected to error/info page
unauthenticated user is redirected to forms-login page for authentication
With my current configuration, scenarios 1 & 3 appear to be working as desired. For scenario 2 I've tried setting RequestHeaderAuthenticationFilter#setExceptionIfHeaderMissing to both true and false.
If setExceptionIfHeaderMissing=false, authenticated/unauthorized request is handled by ExceptionTranslationFilter where AccessDeniedException is thrown and user is redirected to forms-login page.
If setExceptionIfHeaderMissing=true, authenticated/unauthorized request encounters PreAuthenticatedCredentialsNotFoundException from AbstractPreAuthenticatedProcessingFilter.doAuthenticate and HTTP 500 is returned.
So I've read and reread the Spring Security reference and api documents and scoured the web and just can't quite figure out what I need to do. I think I somehow need to enable some kind of filter or handler to trap the PreAuthenticatedCredentialsNotFoundException with a redirected response. But I can't seem to wrap my head around how to implement that with all the spring tools available. Can someone please offer some specifics? Many thanks in advance!!
Here is my configuration:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final String AUTHENTICATION_HEADER_NAME = "PKE_SUBJECT";
#Autowired
CustomUserDetailsServiceImpl customUserDetailsServiceImpl;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(preAuthenticatedAuthenticationProvider());
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
auth.userDetailsService(customUserDetailsServiceImpl);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().and()
.authorizeRequests()
.antMatchers("/javax.faces.resource/**", "/resources/**", "/templates/**", "/public/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/public/welcome.xhtml")
.and()
.addFilter(requestHeaderAuthenticationFilter());
}
#Bean PreAuthenticatedAuthenticationProvider preAuthenticatedAuthenticationProvider() throws Exception {
PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
provider.setPreAuthenticatedUserDetailsService(userDetailsServiceWrapper());
return provider;
}
#Bean
public RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter() throws Exception {
RequestHeaderAuthenticationFilter filter = new RequestHeaderAuthenticationFilter();
filter.setPrincipalRequestHeader(AUTHENTICATION_HEADER_NAME);
filter.setAuthenticationManager(authenticationManagerBean());
filter.setExceptionIfHeaderMissing(true);
return filter;
}
#Bean
public UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>
userDetailsServiceWrapper() throws Exception {
UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> wrapper
= new UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>();
wrapper.setUserDetailsService(customUserDetailsServiceImpl);
return wrapper;
}
}
My customized UserDetailsService:
#Service("customUserDetailsService")
public class CustomUserDetailsServiceImpl implements UserDetailsService {
#Autowired
UserRepo userRepo;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserDetailDO userDetail = userRepo.getUserDetailById(username);
if(userDetail == null) {
throw new UsernameNotFoundException("user is not authorized for this application");
}
List<UserRoleDO> roles = userRepo.getRolesByUsername(username);
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
if(CollectionUtils.isNotEmpty(roles)) {
for(UserRoleDO role : roles) {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role.getRole());
authorities.add(authority);
}
}
UserDetails user = new User(username, "N/A", authorities);
return user;
}
}
I realized that I did not need to handle the exception. What I did was to shift my thinking on this. I realized that even if the username was not found by the customUserDetailsService, the request was still an authenticated request since the request is trusted to be authenticated by the SSO and the proxy server.
So instead of returning a UsernameNotFoundException I returned the org.springframework.security.core.userdetails.User with an empty Authorities collection. And because the RequestHeaderAuthenticationFilter.setExceptionIfHeaderMissing = false by default, no exception is thrown and then the authenticated request is passed to the access filter where it is determined that the request has no authorization to access any resources. So instead of redirecting to the next authentication filter which would be the forms login provider, a 403 Access Denied http status is returned which I can then override to redirect to a user-friendly error page.

Resources