Spring boot create a custom annotation to get logged in user? - java

I have a entity model called User. I have implemented UserDetailsService and used it in WebSecurityConfig like this. Removed unnecessary code for brevity.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
Now at any place during a request-response, I can use this code to get the currently logged in user
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName();
// now use some jpa logic to retrive User entity using above username
Now my question is, is there any way to create a custom AOP or annotation that gives me access to logged in user so that I can use it anywhere like in my controller as shown below?
public class LoginController {
public String response(#CurrentUser User user) {
// do logic once you have access to logged in user with help of #CurrentUser annotation
}
}
So like in the above example, can I create an annotation #CurrentUser that gives me currently logged in user(if not throws an exception, which I can catch in controller advice)?
Note: How I get the user is up to me. In the above example I am using authentcation.getName() and then querying my db to build User entity. But I can use any other way to do so. I just want to create an annotation like CurrentUser and then add the code (of retrieving the user) behind it.

Add the following bean:
(change UserDetail if you are using a different entity for the principal).
#Bean
public Function<UserDetails, User> fetchUser() {
return (principal -> {
String name = principal.getUsername()
//do JPA logic
return ...
});
}
Then set-up the #CurrentUser annotation as followed:
#Retention(RetentionPolicy.RUNTIME)
#Inherited
#Documented
#AuthenticationPrincipal(expression = "#fetchUser.apply(#this)", errorOnInvalidType=true)
public #interface CurrentUser {}

Related

Login/Consume Api rest with Spring Security basic Auth not Working

I'm new to spring security so for learning purpouses I created a api using spring-boot 2.0.3.RELEASE and spring-boot-starter-security.
Here is what I got
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().anyRequest().authenticated().and().httpBasic();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("admin").password(passwordEncoder().encode("admin")).roles("USER");
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Controller class
#RestController
#RequestMapping(value = "/security")
public class AppController {
#Autowired
SpringSecurityService service;
#GetMapping(value = "/{id}")
public Person findOne(#PathVariable("id") final int id) {
return service.findOne(id);
}
}
When I try to consume the get method findOne via some browser a Login form is promped since all browsers support basic auth I try to login using admin as user and password but an error from wrong credentials is shown.
I tried also to consume the mehod via postman sending Authorization and $2a$10$c6MFPW.7MD7a.2V2rJYlXO0.YOLQEmsbu5GBmFsf.jShduBPenQ6O as the value.
I got the value for the password from here:
System.out.println(new BCryptPasswordEncoder().encode("admin"));
I know this is some rookie mistake but I dont know what I'm missing
Tried your code out in my own project. It would appear you're just missing an #Configuration annotation on your WebSecurityConfigurerAdapter thus the default configuration is taking place. Where the user name, and the logged password in the console would work.
Working example - https://github.com/DarrenForsythe/sof-60178305

Spring boot: inject a request-scoped, lazy-initialized custom User object into a controller

I'm building a Spring Boot application to provide a stateless REST API. For security, we're using OAuth 2. My app receives a bearer-only token.
The user's information is stored in our database. I can look it up using the injected Principal in the controller:
#RequestMapping(...)
public void endpoint(Principal p) {
MyUser user = this.myUserRepository.findById(p.getName());
...
}
To avoid this extra line of boilerplate, I would like to be able to inject the MyUser object directly into my controller method. How can I achieve this? (The best I've come up with so far is to create a Lazy, Request-scoped #Bean...but I haven't been able to get it working...)
The Idiomatic Way
The idiomatic way in Spring Security is to use a UserDetailsService or implement your own:
public class MyUserDetailsService implements UserDetailsService {
#Autowired
MyUserRepository myUserRepository;
public UserDetails loadUserByUsername(String username) {
return this.myUserRepository.findById(username);
}
}
And then there are several spots in the Spring Security DSL where this can be deposited, depending on your needs.
Once integrated with the authentication method you are using (in this case OAuth 2.0), then you'd be able to do:
public void endpoint(#AuthenticationPrincipal MyUser myuser) {
}
The Quick, but Less-Flexible Way
It's generally better to do this at authentication time (when the Principal is being ascertained) instead of at method-resolution time (using an argument resolver) as it makes it possible to use it in more authentication scenarios.
That said, you could also use the #AuthenticationPrincipal argument resolver with any bean that you have registered, e.g.
public void endpoint(
#AuthenticationPrincipal(expression="#myBean.convert(#this)") MyUser user)
{
}
...
#Bean
public Converter<Principal, MyUser> myBean() {
return principal -> this.myUserRepository.findById(p.getName())
}
The tradeoff is that this conversion will be performed each time this method is invoked. Since your app is stateless, this might not be an issue (since the lookup needs to be performed on each request anyway), but it would mean that this controller could likely not be reused in other application profiles.
You can achieve this by implementing HandlerMethodArgumentResolver.
For example:
Custom annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
public #interface Version {
}
Implementation:
public class HeaderVersionArgumentResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.getParameterAnnotation(Version.class) != null;
}
#Override
public Object resolveArgument(
MethodParameter methodParameter,
ModelAndViewContainer modelAndViewContainer,
NativeWebRequest nativeWebRequest,
WebDataBinderFactory webDataBinderFactory) throws Exception {
HttpServletRequest request
= (HttpServletRequest) nativeWebRequest.getNativeRequest();
return request.getHeader("Version");
}
}
When you implement this you should add this as argument resolver:
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void addArgumentResolvers(
List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new HeaderVersionArgumentResolver());
}
}
Now we can use it as argument
public ResponseEntity findByVersion(#PathVariable Long id, #Version String version)

Broken spring security adding an aspect

I've just created an aspect to validate an input parameter in my login method. I have a service implementing UserDetailsService interface in spring security (Spring 4.2.1.RELEASE). My aspect is very simple for now, just calling jointPoint.proceed(), not validating input parameter yet:
#Aspect
public class LoginAspect {
#Around(value="#annotation(LoginAnnotation)")
public void loadByUserNameLoginValidation(ProceedingJoinPoint joinPoint) throws Throwable {
joinPoint.proceed();
}
}
My service:
#LoginAnnotation
public class MyUserDetailService implements UserDetailsService {
//Retrieve User Details from database
#LoginAnnotation
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
//Retrieve user details code
//.....
//.....
return userDetails;
}
}
Debugging the code, I've found that loadedUser is null in Spring's DaoAuthenticationProvider.retrieveUser method:
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
The moment I remove my custom annotation #LoginAnnotation from my service, everything works perfectly. What I am missing here?
Yes, that was it:
#Around(value="#annotation(LoginAnnotation)")
public Object loadByUserNameLoginValidation(ProceedingJoinPoint joinPoint) throws Throwable {
return joinPoint.proceed();
}
the result has to be returned from the aspect or it will be null.

Control the order and logic of how spring security runs the list of AuthenticationProviders

Lets say I have multiple AuthenticationProviders in my Spring application. such as :
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(AAuthenticationProvider());
auth.authenticationProvider(BAuthenticationProvider());
auth.authenticationProvider(CAuthenticationProvider());
}
by default spring security will try these providers in order until one provides a non-null response.
Question: Can I customize this behavior where I can can change the running order and logic of the list providers?
It seems that ProviderManager is somehow responsible of running this logic. would it be possible to override this behavior?
I don't think there is a convenient way to manipulate the order rather than just alter the order sequence in which you add your providers with auth.authenticationProvider.
The builder has an ArrayList that's appened at its end each time you call auth.authenticationProvider. And the evaluation is done in the same order as these providers are added to the list as well.
Hope this helps!
I could not find any direct solution from Spring. I was hoping to find something such as the ability to create custom ProviderManager . My workaround solution is to create one Parent authenticationProvider with one parent UserDetailsService where I can control flow of all UserDetailsServices .
You config class will include the following:
#Configuration
#EnableWebSecurity
public class SecConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsService parentUserDetailsService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(ParentAuthenticationProvider());
}
#Bean
public DaoAuthenticationProvider ParentAuthenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(parentUserDetailsService);
return authenticationProvider;
}
}
The parent service will have access to all child services. so it would look something like this:
#Service
public class ParentUserDetailsService implements UserDetailsService {
#Autowired
UserDetailsService aUserDetailsService;
#Autowired
UserDetailsService bUserDetailsService;
#Autowired
UserDetailsService cUserDetailsService;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserDetails user = null;
/* your logic will be here.
You iterate through all of the services
or have some conditional flow. the sky is your limit!
*/
// For Example
if(cond1)
user = aUserDetailsService.loadUserByUsername(username);
else(cond2){
try{
user = bUserDetailsService.loadUserByUsername(username);
}catch(Exception e){
user = cUserDetailsService.loadUserByUsername(username);
}
}
return user;
}
I'm not sure if this is the most optimal solution, but it worked well in my case.

Can not get the #Secured Method Security annotations working in Spring Security

I have done a lot of Research and to me everything looks right... but I cannot get this to work! Anyone has any idea?
No matter what I do, the relevant mapping remains public to anyone (anonymous or logged in, no matter what Role they have).
Ideally I would like to have ALL requests to be Public, except those which are annotated by #Secured() - obviously only the users with the specific roles would be allowed access to these mappings.
Is that possible?
FYI as a workaround I currently built a method "hasRole(String role)" which checks the role of the logged-in user, and throws a NotAuthorizedException (custom made) if the method returns false.
UserDetails
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> grantedAuthorities = null;
System.out.print("Account role... ");
System.out.println(account.getRole());
if (account.getRole().equals("USER")) {
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_USER");
grantedAuthorities = Arrays.asList(grantedAuthority);
}
if (account.getRole().equals("ADMIN")) {
GrantedAuthority grantedAuthorityUser = new SimpleGrantedAuthority("ROLE_USER");
GrantedAuthority grantedAuthorityAdmin = new SimpleGrantedAuthority("ROLE_ADMIN");
grantedAuthorities = Arrays.asList(grantedAuthorityUser, grantedAuthorityAdmin);
}
return grantedAuthorities;
}
SecurityConfig
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthFailure authFailure;
#Autowired
private AuthSuccess authSuccess;
#Autowired
private EntryPointUnauthorizedHandler unauthorizedHandler;
#Autowired
private UserDetailsServiceImpl userDetailsService;
/*#Autowired
public void configAuthBuilder(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(userDetailsService);
}*/
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Autowired
#Override
public void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(userDetailsService);
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().csrfTokenRepository(csrfTokenRepository())
.and().exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
.and().formLogin().loginPage("/login").successHandler(authSuccess).failureHandler(authFailure)
//.and().authorizeRequests().antMatchers("/rest/**").authenticated()
//.and().authorizeRequests().antMatchers("/**").permitAll()
.and().addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class);;
}
AccountController
#Secured("ROLE_USER")
#RequestMapping(method = RequestMethod.GET)
public List<Account> getAllAccounts(#RequestParam(value = "mail", required = false) String mail) {
Thanks!
You can make use of Controller scoped Security with Spring HttpSecurity. Try add this to your configure Method:
.antMatchers("rest/accounts*").hasRole("ADMIN")
And if you wish ANY Request to be public (really?):
.anyRequest().permitAll()
You can additionally secure your Methodinvocation for Example in your UserDetailsService when you access it from anywhere:
#Secured("ROLE_USER")
public getAllAccounts(...){...}
Only then you have to annotate your SecurityConfig with:
#EnableGlobalMethodSecurity(securedEnabled = true)
In practice we recommend that you use method security at your service
layer, to control access to your application, and do not rely entirely
on the use of security constraints defined at the web-application
level. URLs change and it is difficult to take account of all the
possible URLs that an application might support and how requests might
be manipulated. You should try and restrict yourself to using a few
simple ant paths which are simple to understand. Always try to use
a"deny-by-default" approach where you have a catch-all wildcard ( / or
) defined last and denying access. Security defined at the service
layer is much more robust and harder to bypass, so you should always
take advantage of Spring Security’s method security options.
see: http://docs.spring.io/autorepo/docs/spring-security/4.0.0.CI-SNAPSHOT/reference/htmlsingle/#request-matching
Here, I would like to add something based on the above right answer from sven.kwiotek. If in the ROLE table you still want to use "USER", "ADMIN"... the solution is also easy:
When fetch the role from database, do not forget to add "ROLE_" prefix manully, for example,
List<GrantedAuthority> authorities = user.getRoles().stream().map(role ->
new SimpleGrantedAuthority("ROLE_" + role.getRole()))
.collect(Collectors.toList());
and then you could use annotation #Secured("ROLE_USER") in the controller method with safety.
The reason is that in the org.springframework.security.access.vote.RoleVoter class all roles should start with ROLE_ prefix.

Categories

Resources