how to configure security for admins and user separately spring boot - spring-boot

I am trying to secure demo application with jwt token based authentication. My usecase is I have two type of users one is teacher which is admin and other is student which is normal user.
I want to configure a security for teacher and student separately. I have different sources for user authentication — one for teachers and one for normal users. If have configured two webseconfigadapters.
In StudentWebConfigAdapter
config() {
http.antMatcher("/student/getmarks/{id}")
.authorizeRequests().anyRequest().hasRole("USER")
}
#Bean("studentAuth")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
In TeacherWebConfigAdapter
config() {
http.antMatcher("/student/**","/teacher/**")
.authorizeRequests().anyRequest().hasRole("ADMIN")
}
#Bean("teacherAuth")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
How do I configure seperate authentication manager for both. I tried by creating separate bean with different names. it is giving error authentication manager should be only one.
Expecting to only find a single bean for type interface org.springframework.security.authentication.AuthenticationManager, but found [studentAuth, teacherAuth]

Maybe its help for you..
set your group for page in secconfig:
.antMatchers("/page").hasAnyAuthority("USER,ADMIN").anyRequest()
and than you can work with groups in thymeleaf:
<div sec:authorize="hasAuthority('ADMIN')"></div>
or in controller:
#RequestMapping(value = {"/index"}, method =RequestMethod.GET)
public String index(Model model, Principal principal) {
Collection<?> auth =
SecurityContextHolder.getContext().getAuthentication().getAuthorities();
if(auth.contains(new SimpleGrantedAuthority("ADMIN"))
{
return "pages/indexForUser";
}
else if(auth.contains(new SimpleGrantedAuthority("USER")))
{
return "pages/indexForTeacher";
}
//ROLE_ANONYMOUS
}

Related

Spring Boot not blocking request to the endpoint

Im having an issue with spring boot. I am trying to block one specific endpoint called /users/name/ but when i configure it on httpSecurity I can still call the endpoint. I need to block this specific endpoint below is my code.
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class AuthConfigClass extends
WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**").authorizeRequests().antMatchers("/users/name/").permitAll()
.anyRequest().authenticated().and().httpBasic();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception
{
auth.inMemoryAuthentication().withUser("admin")
.password("{noop}password").roles("USER");
}
}
And this is the RestController. Note please that the intention of this app is to make it vulnerable to attacks like the OWASP TOP API so no worries about security issues please tough I accept suggestions.
#Api(value="Users Endpoint and maintenance only for prvileged users")
#RequestMapping("/users")
public class RestControllerMain {
private final UserRespository userRespository;
#Autowired
public RestControllerMain(UserRespository userRespository) {
this.userRespository = userRespository;
}
//Excessive Data Exposure OWASP TOP 10
#RequestMapping(value="/", method=RequestMethod.GET)
public Iterable<User> getAllUsers() {
return userRespository.findAll();
}
#RequestMapping(value="/", method=RequestMethod.POST)
public void UserInsert(#RequestBody User user) {
userRespository.save(user);
}
//null pointer exception and SQL injection OWASP TOP 10 API.
#RequestMapping(value="/name/{user}", method=RequestMethod.GET)
public String mainUser(#PathVariable ("user")String username) {
if(!username.matches("/[\\t\\r\\n]|(--[^\\r\\n]*)|(\\/\\*[\\w\\W]*?(?=\\*)\\*\\/)/gi\n" )) {
return "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘‘VALUE’’.";
}
return "SQL Injection not found";
}
//XSS Also in the OWASP TOP API.
#RequestMapping(value="/search", method=RequestMethod.GET)
public String getMeUSer(#RequestBody User user) {
return "Nice to meet you" + user.getName();
}
// OWASP TOP 10 API. Broken Object Level
#RequestMapping(value="/{id}")
public Optional<User> getUserById(#PathVariable Long id) {
return userRespository.findById(id);
}
}
Please help me figure this I ended up following a tutorial.
As I understood, you want to require authentication for "users/name/{user}" endpoint, but your configuration states
.antMatchers("/users/name/")
whereas it should be
.antMatchers("/users/name/**")
where "**" means any matching pattern. But you if want to grant access after checking the privileges, as you stated in the description of controller, you should configure Spring's authorization and add
#Secured("ROLE_VIEWER, ROLE_ADMIN")
before service or controller methods, which will block any user who doesn't have those roles.

Spring webflux security with mock user for the backend

My spring webflux security code is,
#Bean
public SecurityWebFilterChain securitygWebFilterChain(final ServerHttpSecurity http) {
http.securityContextRepository(securityContextRepository);
return http.authorizeExchange().matchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.pathMatchers(props.getSecurity().getIgnorePatterns()).permitAll().anyExchange().authenticated()
.and().formLogin()
.and().exceptionHandling()
.authenticationEntryPoint((exchange, exception) -> Mono.error(exception))
.accessDeniedHandler((exchange, exception) -> Mono.error(exception))
.and().build();
}
Now, I have the below code to get the logged in user details.
public Mono<AppUserDetails> getUser() {
Mono<Principal> principalMono = ReactiveSecurityContextHolder.getContext().map(SecurityContext::getAuthentication).cast(Principal.class);
principalMono.flatMap(principal -> {
if (principal instanceof AuthenticatedUserToken) {
final AppUserDetails user = ((AuthenticatedUserToken<?>) principal).getUser();
return Mono.just(user);
}
return Mono.error(() -> new IllegalArgumentException("Invalid access"));
}).switchIfEmpty(Mono.defer(() -> {
return Mono.empty();
}));
}
I have an API to create a project and the project table has audit column as well like createdBy user. I am using the above code( getUser() method ) to retrieve the logged in user and get userId from there.
Now, trying to test this API thru postman with just a mock user with userId 1 instead of logging in with the real user as the front end is not ready yet.
How can I run the spring boot app with mock user and return mock user id when the getUser() method is invoked so that I can test end to end.
I am able to achieve this with the code below.
Created a conditional based class for Mock user.
#ConditionalOnProperty(prefix = "admin.service", value = "security.mode", havingValue = "MOCK")
#EnableWebFluxSecurity
public class MockSecurityConfig {
#Autowired
private AppProperties appProps;
#Bean
public SecurityWebFilterChain securitygWebFilterChain(final ServerHttpSecurity http) {
return http.authorizeExchange().pathMatchers("/**").permitAll().and().csrf().disable().build();
}
#Bean
public AuthenticatedPrinciplaProvider mockSecurityPrincipalProvider() {
return new MockSecurityContextPrincipleProvider(props.getSecurity().getMock());
}
}

Idiomatic way to register custom Authentication in Spring Security

My use case is that I'm using Spring Security 5.2's Oauth2 login, but would like my database user class to be available alongside the Oauth2AuthenticationToken within the Authentication. This is so that I have my database user class cached by the SecurityContextHolder.
In Pseudocode:
A user logs in using Google or Github Oauth2
My app finds (or creates) the database user with the information returned
My app saves to the SecurityContextHolder a custom Authentication wrapper that wraps both the Oauth2AuthenticationToken and the database User class
On subsequent requests, the custom Authentication wrapper is available to controller methods
Here are my attempts at a wrapper:
class MyAuthenticationWrapper implements Authentication {
public MyAuthenticationWrapper(User user, Authentication underlyingAuth1) {
this.user = user;
this.underlyingAuth = underlyingAuth1;
}
private final User user;
private final Authentication underlyingAuth;
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return underlyingAuth.getAuthorities();
}
#Override
public void setAuthenticated(boolean isAuthenticated) {
underlyingAuth.setAuthenticated(isAuthenticated);
}
#Override
public String getName() {
return underlyingAuth.getName();
}
#Override
public Object getCredentials() {
return underlyingAuth.getCredentials();
}
#Override
public Object getPrincipal() {
return underlyingAuth.getPrincipal();
}
#Override
public boolean isAuthenticated() {
return underlyingAuth.isAuthenticated();
}
#Override
public Object getDetails() {
return underlyingAuth.getDetails();
}
public User getUser() {
return user;
}
}
And a custom Oauth2AuthenticationFilter:
#Component
class CustomLoginAuthenticationFilter extends OAuth2LoginAuthenticationFilter {
#Autowired
private UserDAO userDAO;
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
Authentication auth = super.attemptAuthentication(request, response);
if (auth instanceof OAuth2AuthenticationToken) {
switch (((OAuth2AuthenticationToken) auth).authorizedClientRegistrationId) {
case "google":
Optional<User> user = userDAO.findByEmail(username);
if (!user.isPresent()) {
throw new NotFoundException("!");
}
return MyAuthenticationWrapper(auth, user.get());
}
}
return auth;
}
}
I haven't had success getting this approach to work, and I'm left wondering if this is the right approach at all.
Is there another, more idiomatic approach to combining database user data with Oauth2 user data in Spring security?
Perhaps looking into OAuth2UserService might help. It gets invoked after successfully obtaining the OAuth token. This is how it would work:
A user logs in using Google or Github Oauth2
No need to add anything. Let the default filters take care of that.
My app finds (or creates) the database user with the information returned
Create your own OAuth2UserService as a bean (it'll get picked up automatically) that takes care of dealing with the database:
#Component
public class CustomService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
#Override
public OAuth2User loadUser(OAuth2UserRequest userRequest)
throws OAuth2AuthenticationException {
// ... DB logic goes here
}
}
In loadUser(...), the OAuth2UserRequest gives you access to the corresponding ClientRegistration and the OAuth2AccessToken, which you can then use to query or update the database.
My app saves to the SecurityContextHolder a custom Authentication wrapper that wraps both the Oauth2AuthenticationToken and the database User class
No need to deal with a wrapper! The custom OAuth2User you construct from information from the database will be the Principal in the OAuth2LoginAuthenticationToken, which ends up being the Authentication, so it'll be available to your application. Since you're not dealing with the Authentication yourself, you wouldn't have to worry about saving it in the SecurityContextHolder.
On subsequent requests, the custom Authentication wrapper is available to controller methods
Your Authentication will be of type OAuth2LoginAuthenticationToken. You can get your custom OAuth2User like this:
OAuth2LoginAuthenticationToken auth = //...
OAuth2User user = auth.getPrincipal();
For more info on the core classes you're dealing with, these might be helpful:
OAuth2LoginAuthenticationFilter
OAuth2LoginAuthenticationProvider
OAuth2UserRequest
OAuth2LoginAuthenticationToken
For out-of-the-box implementations of OAuth2UserService, check out:
DefaultOAuth2UserService
CustomUserTypesOAuth2UserService

How to pass attribute to Spring Controller in a stateless application?

I am currently implementing a SAML SSO solution in my application where in my SAMLUserDetailsService, I am loading my user
#Service
public class SAMLUserDetailsServiceImpl implements SAMLUserDetailsService {
#Override
public Object loadUserBySAML(SAMLCredential credential) throws UsernameNotFoundException {
return new User(credential.getNameID().getValue());
}
}
I am then using a SavedRequestAwareAuthenticationSuccessHandler to redirect user to a landing controller upon successful authentication.
#Bean
public AuthenticationSuccessHandler successRedirectHandler() {
SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler =
new SavedRequestAwareAuthenticationSuccessHandler();
successRedirectHandler.setDefaultTargetUrl("/landing");
return successRedirectHandler;
}
Controller:
#RequestMapping("/landing")
public ResponseEntity landing(User user) {
return ResponseEntity.ok(user.getLoginName());
}
Is there a way to pass the User object to my controller. I noticed that this is usually done using a HandlerMethodArgumentResolver but since my application is stateless and does not use sessions, is there a way to achieve this using another way please?
You don't need injection for this. Use following instead:
SecurityContextHolder.getContext().getAuthentication().getName()
or
SecurityContextHolder.getContext().getAuthentication().getPrincipal()
In the latter case check the type of what getPrincipal() returned. It can be String, it can be UserDetails. If latter, cast it to UserDetails and call getUsername().

spring jwt decoder openid token

External OAuth2 Provider doesn't have public JwkUri, so I tried too override default behavior using following code snippet:
#EnableWebSecurity
public class DirectlyConfiguredJwkSetUri extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("**/oauth2/code/esia**", "**/code/esia**", "**esia**").permitAll()
.antMatchers("/user").fullyAuthenticated()
.anyRequest().authenticated()
.and()
.csrf().disable()
.cors().disable()
.oauth2Client()
.clientRegistrationRepository(this.clientRegistrationRepository)
.authorizationCodeGrant()
.authorizationRequestResolver(new CustomAuthorizationRequestResolver(
this.clientRegistrationRepository, esiaConfig, signatureUtil, timeUtil))
.accessTokenResponseClient(customAccessTokenResponseClient())
.and().and().oauth2Login().tokenEndpoint().accessTokenResponseClient(customAccessTokenResponseClient())
.and().and().oauth2ResourceServer().jwt();
}
#Bean
JwtDecoder jwtDecoder() {
return new CustomJwtDecoder();
}
}
class CustomJwtDecoder implements JwtDecoder {
#Override
public Jwt decode(String token) throws JwtException {
System.out.println(token);
return null;
}
}
However Spring Security somehow still uses default realization and I am getting the following error...
[missing_signature_verifier] Failed to find a Signature Verifier for Client Registration: 'esia'. Check to ensure you have configured the JwkSet URI.
Also, I tried to set custom AuthenticationProvider but spring ignores it.
I guess the catch is that spring`s OAuth2LoginConfigurer method init(B http) calls new OidcAuthorizationCodeAuthenticationProvider(accessTokenResponseClient, oidcUserService)
I was facing the same issue even with 5.2.x release. In my case, the real problem was not in the JwtDecoder. I have fixed the issue by setting the jwk-set-uri property (you can change the provider name by the provider which you are using e.g okta, google etc.) :
security.oauth2.client.provider.azure.jwk-set-uri: https://login.microsoftonline.com/{tenant}/discovery/keys
For 5.1.3.RELEASE it looks like you cannot get around this problem easily.
It stems from the OidcAuthorizationCodeAuthenticationProvider.getJwtDecoder
This happens in line 156 which is a call to a private method
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
...
OidcIdToken idToken = createOidcToken(clientRegistration, accessTokenResponse);
...
}
The one option I see is if you make a copy of this code, and modify it yourself. Extending the class itself is not meaningful because all of the logic happens pretty much in the authenticate method. So you're still overriding it. then you add your provider using the http.authenticationProvider method
Another option is to override the SecurityConfigurerAdapter.postProcess method of the OAuth2LoginConfigurer class and do something clever there. Like populating the JWT decoder map through reflection.
Neither are admittedly preferred solutions. I believe that's why the refactoring happened for the 5.2 release.
Given the latest 5.2.x release then
You're almost there, but you must override the correct bean
#Bean
public JwtDecoderFactory<ClientRegistration> jwtDecoderFactory() {
final JwtDecoder decoder = jwtDecoder();
return context -> decoder;
}
and if you don't want to use lambdas
#Bean
public JwtDecoderFactory<ClientRegistration> jwtDecoderFactory() {
final JwtDecoder decoder = jwtDecoder();
return new JwtDecoderFactory<ClientRegistration>() {
#Override
public JwtDecoder createDecoder(ClientRegistration context) {
return decoder;
}
};
}
How did I figure this out, well I took a look at the OAuth2LoginConfigurer.java class which does
JwtDecoderFactory<ClientRegistration> jwtDecoderFactory = getJwtDecoderFactoryBean();
and the private method that fetches the bean look like this
private JwtDecoderFactory<ClientRegistration> getJwtDecoderFactoryBean() {
ResolvableType type = ResolvableType.forClassWithGenerics(JwtDecoderFactory.class, ClientRegistration.class);
String[] names = this.getBuilder().getSharedObject(ApplicationContext.class).getBeanNamesForType(type);
if (names.length > 1) {
throw new NoUniqueBeanDefinitionException(type, names);
}
if (names.length == 1) {
return (JwtDecoderFactory<ClientRegistration>) this.getBuilder().getSharedObject(ApplicationContext.class).getBean(names[0]);
}
return null;
}
(Found this while looking for a solution to overriding the Jwt and Oidc Token validation. Filip's answer helped me get to the solution so I figured I'd add this to help anyone who follows the same search.)
For a time-travel testing scenario, our jvm clock was set months in the future. Login was failing due to the validations done on Jwt and Oidc token timestamp.
This addition worked for our app on Spring Security 5.2.1
#Bean
public JwtDecoderFactory<ClientRegistration> getJWTDecoder() {
OidcIdTokenDecoderFactory factory = new OidcIdTokenDecoderFactory();
factory.setJwtValidatorFactory(new Function<ClientRegistration, OAuth2TokenValidator<Jwt>>() {
#Override
public OAuth2TokenValidator<Jwt> apply(ClientRegistration clientRegistration) {
return new CustomTimestampIgnoringOidcTokenValidator(clientRegistration);
}
});
}
This just replaces the Default validators with a custom one which only validates the other claims.

Resources