Spring Security #PreAuthorize annotation with AuthenticationSuccessEvent - spring

I have basic authentication and i want to log/do something on succesfull log in but AuthenticationSuccessEvent is firing up even if that ROLE dont have permission i need to access that HTTP method. How can i log/do something ONLY if i can access that HTTP method with permission.
#Component("authorization")
public class AuthorizationUtils {
public boolean hasPermission(final String permission) {
Collection<? extends GrantedAuthority> authorities = getUserDetails().getAuthorities();
return authorities.contains(new SimpleGrantedAuthority(permission));
}
public static UserDetails getUserDetails() {
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
if (authentication != null && authentication.getPrincipal() instanceof UserDetails) {
return (UserDetails) authentication.getPrincipal();
}
return null;
}
}
public class AuthenticationEventListener implements ApplicationListener<AuthenticationSuccessEvent> {
#Autowired
private HttpServletRequest request;
#Override
public void onApplicationEvent(AuthenticationSuccessEvent event) {
String username = ((UserAccountDetails) event.getAuthentication().getPrincipal()).getUsername();
log.trace("Account with username : {} successfully log in!", username);
System.out.println(request.getHeader("Authorization"));
}
#GetMapping("/permission")
#PreAuthorize("#authorization.hasPermission('edit.helloworld123')")
public String editHelloWorld(){
return "Welcome to permission edit.helloWorld!";
}

Authorization is not the same as Authentication .Authentication is about whether an user can login successfully or not. Authorization is about if the login user have enough permission to execute a method. So even an user is successfully login (i.e AuthenticationSuccessEvent happens) , if he does not have enough permission to execute a method, the method still fail to execute.
What you are really want is to detect the authorization success event , but you are now detecting the authentication success event , which are totally different things.
To enable spring-security to publish the authorization success event after an user who has enough permission execute a protected method successfully , you have to enable setPublishAuthorizationSuccess of the MethodInterceptor.
From the docs to override the default settings related to method security, you can extend GlobalMethodSecurityConfiguration :
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Override
public MethodInterceptor methodSecurityInterceptor(MethodSecurityMetadataSource methodSecurityMetadataSource) {
MethodInterceptor result = super.methodSecurityInterceptor(methodSecurityMetadataSource);
((MethodSecurityInterceptor) result).setPublishAuthorizationSuccess(true);
return result;
}
}
And AuthorizedEvent will be published after the user has enough permission to execute a protected method .So create a ApplicationListener to listen and handle it :
#Component
public class MyEventListener implements ApplicationListener<AuthorizedEvent> {
#Override
public void onApplicationEvent(AuthorizedEvent event) {
//Handle the AuthorizedEvent event here......
}
}

Related

How to set a custom principal object during or after JWT authentication?

I've changed the way a user is authenticated in my backend. From now on I am receiving JWT tokens from Firebase which are then validated on my Spring Boot server.
This is working fine so far but there's one change which I am not too happy about and it's that the principal-object is now a org.springframework.security.oauth2.jwt.Jwt and not a AppUserEntity, the user-model, like before.
// Note: "authentication" is a JwtAuthenticationToken
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Jwt jwt = (Jwt) authentication.getPrincipal();
So, after some reading and debugging I found that the BearerTokenAuthenticationFilter essentially sets the Authentication object like so:
// BearerTokenAuthenticationFilter.java
AuthenticationManager authenticationManager = this.authenticationManagerResolver.resolve(request);
// Note: authenticationResult is our JwtAuthenticationToken
Authentication authenticationResult = authenticationManager.authenticate(authenticationRequest);
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authenticationResult);
SecurityContextHolder.setContext(context);
and as we can see, this on the other hand comes from the authenticationManager which is a org.springframework.security.authentication.ProviderManager and so on. The rabbit hole goes deep.
I didn't find anything that would allow me to somehow replace the Authentication.
So what's the plan?
Since Firebase is now taking care of user authentication, a user can be created without my backend knowing about it yet. I don't know if this is the best way to do it but I intend to simply create a user record in my database once I discover a valid JWT-token of a user which does not exist yet.
Further, a lot of my business logic currently relies on the principal being a user-entity business object. I could change this code but it's tedious work and who doesn't want to look back on a few lines of legacy code?
I did it a bit different than Julian Echkard.
In my WebSecurityConfigurerAdapter I am setting a Customizer like so:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.oauth2ResourceServer()
.jwt(new JwtResourceServerCustomizer(this.customAuthenticationProvider));
}
The customAuthenticationProvider is a JwtResourceServerCustomizer which I implemented like this:
public class JwtResourceServerCustomizer implements Customizer<OAuth2ResourceServerConfigurer<HttpSecurity>.JwtConfigurer> {
private final JwtAuthenticationProvider customAuthenticationProvider;
public JwtResourceServerCustomizer(JwtAuthenticationProvider customAuthenticationProvider) {
this.customAuthenticationProvider = customAuthenticationProvider;
}
#Override
public void customize(OAuth2ResourceServerConfigurer<HttpSecurity>.JwtConfigurer jwtConfigurer) {
String key = UUID.randomUUID().toString();
AnonymousAuthenticationProvider anonymousAuthenticationProvider = new AnonymousAuthenticationProvider(key);
ProviderManager providerManager = new ProviderManager(this.customAuthenticationProvider, anonymousAuthenticationProvider);
jwtConfigurer.authenticationManager(providerManager);
}
}
I'm configuring the NimbusJwtDecoder like so:
#Component
public class JwtConfig {
#Bean
public JwtDecoder jwtDecoder() {
String jwkUri = "https://www.googleapis.com/service_accounts/v1/jwk/securetoken#system.gserviceaccount.com";
return NimbusJwtDecoder.withJwkSetUri(jwkUri)
.build();
}
}
And finally, we need a custom AuthenticationProvider which will return the Authentication object we desire:
#Component
public class JwtAuthenticationProvider implements AuthenticationProvider {
private final JwtDecoder jwtDecoder;
#Autowired
public JwtAuthenticationProvider(JwtDecoder jwtDecoder) {
this.jwtDecoder = jwtDecoder;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
BearerTokenAuthenticationToken token = (BearerTokenAuthenticationToken) authentication;
Jwt jwt;
try {
jwt = this.jwtDecoder.decode(token.getToken());
} catch (JwtValidationException ex) {
return null;
}
List<GrantedAuthority> authorities = new ArrayList<>();
if (jwt.hasClaim("roles")) {
List<String> rolesClaim = jwt.getClaim("roles");
List<RoleEntity.RoleType> collect = rolesClaim
.stream()
.map(RoleEntity.RoleType::valueOf)
.collect(Collectors.toList());
for (RoleEntity.RoleType role : collect) {
authorities.add(new SimpleGrantedAuthority(role.toString()));
}
}
return new JwtAuthenticationToken(jwt, authorities);
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(BearerTokenAuthenticationToken.class);
}
}
This is working fine so far but there's one change which I am not too happy about and it's that the principal-object is now a org.springframework.security.oauth2.jwt.Jwt and not a AppUserEntity, the user-model, like before.
In my application I have circumvented this by rolling my own JwtAuthenticationFilter instead of using BearerTokenAuthenticationFilter, which then sets my User Entity as the principal in the Authentication object. However, in my case this constructs a User barely from the JWT claims, which might be bad practice: SonarLint prompts to use a DTO instead to mitigate the risk of somebody injecting arbitrary data into his user record using a compromised JWT token. I don't know if that is a big deal - if you can't trust your JWTs, you have other problems, IMHO.
I don't know if this is the best way to do it but I intend to simply create a user record in my database once I discover a valid JWT-token of a user which does not exist yet.
Keep in mind that JWTs should be verified by your application in a stateless manner, solely by verifying their signature. You shouldn't hit the database every time you verify them. Therefor it would be better if you create a user record using a method call like
void foo(#AuthenticationPrincipal final Jwt jwt) {
// only invoke next line if reading JWT claims is not enough
final User user = userService.findOrCreateByJwt(jwt);
// TODO method logic
}
once you need to persist changes to the database that involve this user.
Since
SecurityContextHolder.setContext(context);
won't work for
request.getUserPrincipal();
you may create a custom class extending HttpServletRequestWrapper
import java.security.Principal;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
public class UserPrincipalHttpServletRequest extends HttpServletRequestWrapper {
private final Principal principal;
public UserPrincipalHttpServletRequest(HttpServletRequest request, Principal principal) {
super(request);
this.principal = principal;
}
#Override
public Principal getUserPrincipal() {
return principal;
}
}
then in your filter do something like this:
protected void doFilterInternal(HttpServletRequest request){
. . .
// create user details, roles are required
Set<GrantedAuthority> authorities = new HashSet<>();
authorities.add(new SimpleGrantedAuthority("SOME ROLE"));
UserDetails userDetails = new User("SOME USERNAME", "SOME PASSWORD", authorities);
// Create an authentication token
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// follow the filter chain, using the new wrapped UserPrincipalHtppServletRequest
chain.doFilter(new UserPrincipalHttpServletRequest(request, usernamePasswordAuthenticationToken), response);
// all filters coming up, will be able to run request.getUserPrincipal()
}
According Josh Cummings answer in issue #7834 make configuration:
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
http...
.oauth2ResourceServer(oauth2 -> oauth2.jwt(
jwt -> jwt.jwtAuthenticationConverter(JwtUtil::createJwtUser)))
...
return http.build();
}
and implement factory method, e.g:
public class JwtUtil {
public static JwtUser createJwtUser(Jwt jwt) {
int id = ((Long) jwt.getClaims().get("id")).intValue();
String rawRoles = (String) jwt.getClaims().get("roles");
Set<Role> roles = Arrays.stream(rawRoles.split(" "))
.map(Role::valueOf)
.collect(Collectors.toSet());
return new JwtUser(jwt, roles, id);
}
}
public class JwtUser extends JwtAuthenticationToken {
public JwtUser(Jwt jwt, Collection<? extends GrantedAuthority> authorities, int id) {
super(jwt, authorities);
....
}
}
Take in note, that controller's methods should inject JwtUser jwtUser without any #AuthenticationPrincipal

Custom Principal with OAuth2 in existing form login application

I'm trying to add OAuth2 login to an existing form login application. So far I've added the required configuration to get Google auth working and the goal is to enable existing username/password users (or new users) to login both ways.
All my controllers rely on my UserDetails implementation:
public class User implements UserDetails {
private Long id;
private String email;
private String password;
private String googleAccessToken;
// ...
And I can get the logged user in controllers like this:
#GetMapping
public String index(#AuthenticationPrincipal User user, Model model) {
So what I've done is to implement my custom OAuth2UserService to fetch the existing user from the database but I can't find the way to set the User as the principal.
In previous versions of the OAuth2 integration it seemed to exist a simpler solution based on PrincipalExtractor but it is no longer available.
#Service
#RequiredArgsConstructor
public class OAuth2UserDetailsService implements OAuth2UserService<OidcUserRequest, OidcUser> {
private final UsersRepository usersRepository;
#Override
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
final OidcUserService delegate = new OidcUserService();
User user;
// Delegate to the default implementation for loading a user
OidcUser oidcUser = delegate.loadUser(userRequest);
switch (userRequest.getClientRegistration().getClientName()) {
case "google":
user = usersRepository.findOneByGoogleAccessToken(userRequest.getAccessToken());
break;
default:
throw new OAuth2AuthenticationException(new OAuth2Error("invalid_token"));
}
// here I should return my user principal
return new DefaultOidcUser(null, null);
}
Any ideas?
Finally solved this returning an instance of OidcUser:
public class UserPrincipal implements UserDetails, OidcUser {
// ...
}
And in the OAuth2UserService:
#Override
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
final OidcUserService delegate = new OidcUserService();
User user;
// Delegate to the default implementation for loading a user
OidcUser oidcUser = delegate.loadUser(userRequest);
// ...
return new UserPrincipal(user, oidcUser);
}

spring boot login check implement way

My way of implementing login check
#Configuration
public class ViewControllerImpl implements WebMvcConfigurer {
#Bean
public WebMvcConfigurer webMvcConfigurer(){
WebMvcConfigurer adapter = new WebMvcConfigurer() {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new InterceptorConfig()).addPathPatterns("/**")
.excludePathPatterns("/login", "/", "/session", "/static/**");
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
WebMvcConfigurer.super.addResourceHandlers(registry);
}
};
return adapter;
}
}
public class InterceptorConfig implements HandlerInterceptor
{
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
{
Object loginStatus = request.getSession().getAttribute("loginStatus");
if(loginStatus == "success"){
return true;
}
else {
// request.getRequestDispatcher("login").forward(request, response);
response.sendRedirect("/login");
return false;
}
}
}
After the username and password are successfully verified,I will store a loginstatus in the session.
if loginstatus is success, it means that you have logged in.
Is this code implementation safe?
Do I need to use spring security?
Like #m-deinum already said you should really just utilize Spring-Security. There is a super quick tutorial on how to do this here: https://spring.io/guides/gs/securing-web/
You will start to learn it with BasicAuth and an InMemoryDB for user and password storage. You will end up with an application that returns a status Code 200 for logged in user or a status code 401 for a denied access.
From there on it is easy to extend your application to user user and password stored in a custom db or even OAuth.
Have no fear using it - its easy and fun :)

Spring security returns wrong loggedin user AFTER override UserDetailsContextMapper

I am using spring security with LDAP in a spring boot application.
Everything was working fine before I did below changes in security config as below.
Override UserDetailsContextMapper to set ROLE for the users.
Restrict URLs based on user role.
Below are the code changes to achieve above 2 things.
1. UserDetailsContextMapper:
#Component
public class UserDetailsContextMapperImpl implements UserDetailsContextMapper, Serializable{
private static final long serialVersionUID = 3962976258168853954L;
private static Logger logger = LoggerFactory.getLogger(UserDetailsContextMapperImpl.class);
#SuppressWarnings("rawtypes")
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authority) {
List<GrantedAuthority> mappedAuthorities = new ArrayList<GrantedAuthority>();
try{
Attributes attrListOuter = ctx.getAttributes();
Attribute attrListInner = attrListOuter.get(UserConstant.MEMBER_OF);
logger.info("memberOf: " + attrListInner.getID());
for (NamingEnumeration enumInner = attrListInner.getAll(); enumInner.hasMore();){
String CN = (String)enumInner.next();
logger.info("CN value: " + CN);
if(CN.contains(UserConstant.MASTER_GROUP_PROJ_NAME)){
logger.info("Logged in user is authorized to acccess Rates Toronto application: {}", username );
mappedAuthorities.add(new SimpleGrantedAuthority(UserConstant.ROLE_ABC));// adding ROLE_ABC to APP/LDAP users.
logger.info("User {} Role set as : {}", username, UserConstant.ROLE_ABC );
break;
}else if(CN.contains(UserConstant.GROUP_XYZ)){
mappedAuthorities.add(new SimpleGrantedAuthority(UserConstant.ROLE_XYZ));
logger.info("User {} Role set as : {}", username, UserConstant.ROLE_XYZ );
break;
}
}
if(mappedAuthorities.isEmpty()){
logger.info("Logged in user is NOT authorized to access ABCD application : {}", username );
}
}catch(Exception ex){
logger.info("Exception while mapping UserDetails with LDAP" + ex.getMessage());
}
//Returning Spring Seurity's User object.
return new org.springframework.security.core.userdetails.User(username, "", true, true, true, true, mappedAuthorities);
}
2. Restrict URLs based on user role:
In my websecurity configuration class,
#Configuration
#EnableWebSecurity
public class AbcWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
.......
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/api/**").hasAnyRole("REST","ABC").and().httpBasic().and().formLogin();
http.authorizeRequests().antMatchers("/xyz/**").hasRole("XYZ").and().httpBasic();
http.authorizeRequests().antMatchers("/abc/**").hasRole("ABC").and().httpBasic(); // Users who are accessing the URLS/application via application's UI. ie. Business User.
http.headers().contentTypeOptions().xssProtection().cacheControl().httpStrictTransportSecurity().frameOptions().disable();
http.headers().addHeaderWriter(new StaticHeadersWriter("Cache-Control","no-cache, no-store, max-age=0, must-revalidate"));
http.headers().addHeaderWriter(new StaticHeadersWriter("Expires","0"));
http.csrf().disable();
http.sessionManagement().maximumSessions(1).sessionRegistry(sessionRegistry());
super.configure(http);
}
and ......
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication().userDetailsContextMapper(userDetailsContextMapperImpl).userSearchFilter(SAM_ACC).userSearchBase(base).contextSource().url(url).managerDn(managerDn).managerPassword(password);
auth.inMemoryAuthentication().withUser(restUserName).password(restPassword).roles(restRole);
//This publisher will trigger AuthenticationFailureBadCredentialsEvent (AbstractAuthenticationFailureEvent)
auth.authenticationEventPublisher(new DefaultAuthenticationEventPublisher(applicationEventPublisher));
}
After the above changes, getting loggedIn user giving wrong user ( sometimes),
This is not happening every-time, but happening intermittently.
I am using below code to get the current logged in user.
Authentication authentication = securityContextHolder.getContext().getAuthentication();
if (authentication == null)
return null;
org.springframework.security.core.userdetails.User userDetails = (org.springframework.security.core.userdetails.User) authentication.getPrincipal();
String userName = userDetails.getUsername();
I am unable to find where I am missing, any pointers/direction will be very helpful.
It is returning wrong user from already authenticated users.
I am using spring boot 1.2.0 which uses spring security 3.2.5 by default

Checking Principal-user by Injected SecurityContext inside the constructor of a web resource

I'm relatively new to Jersey and JAX-RS, but I can't find my way in the documentation about the use of the SecurityContext inside the constructor of a Resource.
Our OAUTH implementation is currently such that all requests on the REST API are intercepted by an implementation of the ResourceFilter. So the resource filter sets a user Principal by calling:
containerRequest.setSecurityContext(new MySecurityContext(user));
... inside the ContainerRequest.filter() method, wWith a concrete class MySecurityContext, as:
public class MySecurityContext implements SecurityContext {
private final User principal;
public MySecurityContext(final User user) {
this.principal = user;
}
#Override
public String getAuthenticationScheme() {
return SecurityContext.FORM_AUTH;
}
#Override
public Principal getUserPrincipal() {
return principal;
}
#Override
public boolean isSecure() {
return false;
}
#Override
public boolean isUserInRole(String role) {
return principal.getRoles().contains(role);
}
}
Now I want to use that user principal in the constructor of the webresource before any #GET/#POST/#DELETE method is called. I've tried injecting
#Context SecurityContext
..into the constructor argument list but a call to getUserPrincipal() just yields null.
Is there a way to check the Principal user in the constructor of a Webresource?
thanks!
It works for me if I inject it into my API endpoint:
#POST
#ResourceFilters({MyAuthenticationFilter.class})
public Response foo(#Context HttpServletRequest request, #Context SecurityContext security) {
final Principal principal = security.getUserPrincipal();
[...]
return Response.status(Response.Status.OK).build();
}

Resources