I'm trying to use authentication by google. I am using springboot2, so most of the configuration is automatic. The authentication itself works good, but afterwards I would like to populate Principal with my own data (roles, username, and stuff).
I've created MyUserService that exteds DefaultOauth2UserService, and I am trying to use it as follows:
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
MyUserService myUserService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2Login()
.userInfoEndpoint()
.userService(myUserService);
}
}
I've checked with debuger, that application never actually uses loadUser methods. And here is implementation of MyUserService:
#Component
public class MyUserService extends DefaultOAuth2UserService {
#Autowired
UserRepository userRepository;
public MyUserService(){
LoggerFactory.getLogger(MyUserService.class).info("initializing user service");
}
#Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
Map<String, Object> attributes = oAuth2User.getAttributes();
String emailFromGoogle = (String) attributes.get("email");
User user = userRepository.findByEmail(emailFromGoogle);
attributes.put("given_name", user.getFirstName());
attributes.put("family_name", user.getLastName());
Set<GrantedAuthority> authoritySet = new HashSet<>(oAuth2User.getAuthorities());
return new DefaultOAuth2User(authoritySet, attributes, "sub");
}
}
Actually the solution was just to add another property for google authentication:
spring.security.oauth2.client.registration.google.scope=profile email
Not sure, what is the default scope, and why entrance to the service is dependent on scope, but without this line the code never reached my custom service.
I think you're missing the #EnableOAuth2Client annotation at the top of your SecurityConfig class.
Regardless, I made an examplewith a Custom user service for oauth2 here https://github.com/TwinProduction/spring-security-oauth2-client-example/ if it helps
Related
So, I have the following scenario: I need to configure Spring Security for the Actuator endpoints. All this should be in an external library, as part of an autoconfiguration class dependent on a certain property. Access to some of those endpoints should only be allowed for a special role (I've configured an InMemoryUserDetailsManager just for the sake of it).
The applications using this external library can have their own security configurations. If the UserDetailsService bean does not exist in the app context the autoconfigured one is created. The problem is the following - if I declare an UserDetailsService in the app configuration, the autoconfiguration class updates it with the actuator admin user (and I can see that both users exist): the security configuration is all messed up though (I am getting 401 when using the correct credentials).
Does anyone know how to manage this?
The autoconfiguration class (in external library - Kotlin):
#Configuration
#EnableConfigurationProperties(Props::class)
#ConditionalOnProperty(prefix = Props.PROPERTY_PREFIX, name = ["securityEnabled"])
#ConditionalOnWebApplication(type = SERVLET)
open class ActuatorSecurityAutoConfiguration(private val props: Props) {
#Bean
#ConditionalOnMissingBean(PasswordEncoder::class)
public open fun passwordEncoder(): PasswordEncoder {
return BCryptPasswordEncoder()
}
#Bean
#ConditionalOnBean(UserDetailsService::class)
public open fun actuatorUserDetailsManager(passwordEncoder: PasswordEncoder,
userDetailsManager: UserDetailsManager): UserDetailsManager {
userDetailsManager.createUser(
User.withUsername(props.username)
.password(props.password)
.passwordEncoder(passwordEncoder::encode)
.roles(ACTUATOR_USER_ROLE)
.build())
return userDetailsManager
}
#Bean
#ConditionalOnMissingBean(UserDetailsService::class)
public open fun defaultActuatorUserDetailsManager(passwordEncoder: PasswordEncoder): UserDetailsManager {
return InMemoryUserDetailsManager(listOf(
User.withUsername(props.username)
.password(props.password)
.passwordEncoder(passwordEncoder::encode)
.roles(ACTUATOR_USER_ROLE)
.build()
))
}
#Bean
#Throws(Exception::class)
#Order(100) //the order is not respected as this is loaded after the main config class
public open fun actuatorSecurityFilterChain(httpSecurity: HttpSecurity): SecurityFilterChain {
httpSecurity
.antMatcher("/actuator/**")
.authorizeHttpRequests {
it.antMatchers("/actuator/info", "/actuator/health").permitAll()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.anyRequest().hasRole(ACTUATOR_USER_ROLE)
}
.headers().disable()
.csrf().disable()
.httpBasic()
return httpSecurity.build()
}
The configuration class in the application using the external library:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration {
#Value("${credentials.username}")
private String username;
#Value("${credentials.password}")
private String password;
#Value("${credentials.role}")
private String role;
private final AuthClient authClient;
public DashboardSecurityConfiguration(AuthClient authClient) {
this.authClient = authClient;
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Order(1)
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity.antMatcher("/secure_endpoint/**")
.authorizeHttpRequests(authorize -> authorize
.antMatchers(HttpMethod.OPTIONS).permitAll()
.anyRequest().hasRole(role)
)
.authenticationProvider(new SsoAuthenticationProvider(authClient))
.addFilterBefore(new SsoTokenFilter(), UsernamePasswordAuthenticationFilter.class)
.httpBasic();
return httpSecurity.build();
}
#Bean
#Order
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeHttpRequests(authorize -> authorize
.anyRequest().permitAll())
.headers().disable()
.csrf().disable();
return httpSecurity.build();
}
#Bean
public UserDetailsService userDetailsManager() {
return new InMemoryUserDetailsManager(List.of(
createUser(username, password, role)
));
}
private UserDetails createUser(String username, String password, String... roles) {
return User.builder()
.username(username)
.password(passwordEncoder().encode(password))
.roles(roles)
.build();
}
application.properties:
prefix.securityEnabled=true
I can access /actuator endpoints without issues (even the secured paths) if I comment out the UserDetailsService in the Java application, but I can no longer access them (including /secure_endpoint) once I include it back in.
Any thoughts? Many thanks!
Imagine the following (hypothetical) data structure
endpoint | username | password
users admin 123
info george awd
data magnus e4
this means that every endpoint requires different credentials and no one username/password combo can log in to every endpoint. I am looking for a way to make this scalable in our Spring MVC project when adding more endpoints. We could use roles and hardcore this into the config class but the endpoints and login combinations vary for every customer installation
Given the following SecurityConfiguration with LookupAuthenticationService being the class that looks up the username/password data in the database
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private static final String[] ENDPOINT_LIST = {
"/rest/**"
};
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(ENDPOINT_LIST)
.authenticated()
.and()
.httpBasic();
}
#Autowired
protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService());
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected UserDetailsService userDetailsService() {
return new LookupAuthenticationService(passwordEncoder());
}
}
The ideal situation would be if LookupAuthenticationService has access to the request so we know which endpoint to fetch but I guess this is only possible when working with individual Filters
The possibilities I've found so far are:
Add a WebSecurityConfigurerAdapter and multiple UserDetailsServer specific per endpoint -> lots of code
Add a HandlerInterceptor per endpoint -> lots of code
AuthenticationManagerResolver returning a different AuthenticationManager based on pathInfo?
Any input how to best resolve this issue would be appreciated
You can have a table where you map endpoints to rules, like so:
pattern
authority
/users/**
ROLE_ADMIN
/info/**
ROLE_USER
/another/**
ROLE_ANOTHER
And instead of assigning a user to an endpoint, you assign a role to the users. With this in place, you can create an AuthorizationManager which is going to protect your endpoints based on the request path.
#Component
public class AccessRuleAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {
private final AccessRuleRepository rules;
private RequestMatcherDelegatingAuthorizationManager delegate;
public AccessRuleAuthorizationManager(AccessRuleRepository rules) {
this.rules = rules;
}
#Override
public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext object) {
return this.delegate.check(authentication, object.getRequest());
}
#EventListener
void applyRules(ApplicationReadyEvent event) {
Builder builder = builder();
for (AccessRule rule : this.rules.findAll()) {
builder.add(
new AntPathRequestMatcher(rule.getPattern()),
AuthorityAuthorizationManager.hasAuthority(rule.getAuthority())
);
}
this.delegate = builder.build();
}
}
And, in your SecurityConfiguration you simply do this:
#Autowired
private AccessRuleAuthorizationManager access;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authz) ->
authz.anyRequest().access(this.access)
)
.httpBasic(Customizer.withDefaults());
}
I recommend you to take a look at this repository and watch the presentation from the repository's description. The last steps of the presentation was adding the custom AuthorizationManager, and there's a great explanation about it.
What I have currently
I'm currently implementing an OIDC Resource Provider for my company. They use their intern OIDC servers, which I managed to work with by following this example: https://github.com/jgrandja/oauth2login-demo/tree/linkedin
I'm now able to retrieve user information from the Authorization Server, like that:
#RestController
#RequestMapping("/some/route")
public class SomeController {
#GetMapping("/some/route")
public ResponseEntity<?> getSomething(#RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient authorizedClient) {
String userInfoEndpointUri = authorizedClient.getClientRegistration()
.getProviderDetails().getUserInfoEndpoint().getUri();
Map userAttributes = this.webClient
.get()
.uri(userInfoEndpointUri)
.attributes(oauth2AuthorizedClient(authorizedClient))
.retrieve()
.bodyToMono(Map.class)
.block();
String firstName = (String) userAttributes.get("first_name");
String lastName = (String) userAttributes.get("last_name");
...
}
}
What I'd like
I am now searching for a solution to map the userAttributes to an Object prior to
the controller method call, so that I get e.g.:
#GetMapping("/some/route")
public ResponseEntity<?> getSomething(MyCostumUserBean user) {
String firstName = user.getFirstName();
String lastName = user.getLastName();
...
}
I read something about the ChannelInterceptor and HandlerInterceptor and also the PrincipalExtractor and AuthoritiesExtractor.
The problem is, that I am just learning the Spring framework and these possibilities are overwhelming me.
It would be a plus if that method would allow some validation and would immediately respond with Error codes if the validation fails.
After that is achieved, I would like to add additional information to MyCostumUserBean from another server, which I send the identity of the current session's user to and receive e.g. Role/Permissions of that user.
I tried to put it in a picture:
Question
What is the proper / by the Spring Framework intended way to deal with that? How do I achieve that?
Extra: Is it secure to rely on OAuth2AuthorizedClient.getPrincipalName()? Or can that be faked by an user, by faking the Cookie/Token?
I think you are asking the way to configure the success handler, or a filter which can check the user attributes.
If this is what you are asking, There are many ways to do it.
For examples:
Use User scope check:(need to assign the scope to the user in advance.)
#ResponseBody
#GetMapping("/some/route")
public String getSomeThing(#RegisteredOAuth2AuthorizedClient("custom") OAuth2AuthorizedClient authorizedClient) {
Set<String> scopes = authorizedClient.getAccessToken()
.getScopes();
if (scopes.contains("users:read")) {
} else if (scopes.contains("users:read")) {
return " page 1";
} else {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Forbidden.");
}
}
You can put some logic in the successHandler:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/**")
.permitAll().and()
.formLogin()
.successHandler(successHandler());
}
#Bean
public CustomSuccessHandler successHandler() {
return new CustomSuccessHandler();
}
If you want to apply a filter for your Security Chians:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
...
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
...
.and()
.addFilterBefore(getBeforeAuthenticationFilter(), CustomBeforeAuthenticationFilter.class)
.formLogin()
.loginPage()
.permitAll()
...
}
public UsernamePasswordAuthenticationFilter getBeforeAuthenticationFilter() throws Exception {
CustomBeforeAuthenticationFilter filter = new CustomBeforeAuthenticationFilter();
....
return filter;
}
}
You can also achieve the same purpose by using a Customizing Filter Chains, by give the different order and the relative login in it.
#Configuration
#Order(SecurityProperties.BASIC_AUTH_ORDER - 10)
public class ApplicationConfigurerAdapterForUserGroup1 extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
...;
}
}
#Configuration
#Order(SecurityProperties.BASIC_AUTH_ORDER - 10)
public class ApplicationConfigurerAdapterForUserGroup2 extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
...;
}
}
In Spring MVC with Spring Security, is it possible to achieve this?
#Override WebSecurityConfigurerAdapter.configure(HttpSecurity)
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.authorizeRequests()
.mvcMatchers("/users/{authentication.principal.username}").hasAnyRole(ADMIN, MANAGER)
.antMatchers("/users/**").hasRole(ADMIN)
.anyRequest().authorized()
...
}
/users/** is a restricted area and should be accessible by admins only. But managers should still be able to see their own profile (/users/user_with_manager_role), and only their own profile, not those of any other users (regardless of their role).
Solution
I've found a solution in Andrew's answer. My Code now looks like this:
WebSecurityConfigurerAdapter
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true) // added this annotation
public class SecurityConfig extends WebSecurityConfigurerAdapter
#Override WebSecurityConfigurerAdapter.configure(HttpSecurity)
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.authorizeRequests()
// removed /users handling
.anyRequest().authorized()
...
}
UsersController
#Controller
#RequestMapping("/users")
public class UsersController
{
#GetMapping("{username}")
#PreAuthorize("authentication.principal.username == #username) || hasRole('ADMIN')")
public String usersGet(#PathVariable("username") String username)
{
// do something with username, for example get a User object from a JPA repository
return "user";
}
}
I'm afraid it's not possible: when this configuration is being set up, it has no info about {authentication.principal.username} which will be resolved at some point in future.
But Spring gives you a bunch of built-in method security expressions you can annotate your methods with.
Starting from a simple expression like #PreAuthorize("hasRole('ADMIN')"), you might end up with a custom one:
#XMapping(path = "/users/{username}")
#PreAuthorize("#yourSecurityService.isMyPage(authentication.principal, #username)")
public void yourControllerMethod(#PathVariable String username);
#yourSecurityService.isMyPage(authentication.principal, #username) refers to your #Service method public boolean isMyPage(Principal, String).
How about something like this:
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/myself").hasAnyRole(ADMIN, MANAGER)
.antMatchers("/users/**").hasRole(ADMIN)
.anyRequest().hasAnyRole(ADMIN, MANAGER)
...
}
#RequestMapping(value = "/myself", method = RequestMethod.GET)
public Profile getMyself() {
// return the profile of the loged in user
}
With this manager and admins can get their own profile and admins can also request other profiles with /users/{username}.
I want to restrict some JSP pages according to the UserRole in my spring boot app
for this i have seen so many examples like:-
http.authorizeRequests().antMatchers("/login").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
but my problem is i cant hardcode the UserRoles like ADMIN or USER because i have new user roles created in different occasions so i can't hardcode the exact user-roles.i have the information about the user-roles that can access a list of jsp pages in my database and here iam using spring security and iam newbie to spring boot and spring security.
edit
my config class is
#Configuration
#EnableWebSecurity
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
private UserDetailsService userDetailsService;
#Autowired
CustomAuthHandler customAuthenticationHandler;
#Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
System.out.println(1);
return new BCryptPasswordEncoder();
}
#Bean
CustomAuthHandler authenticationHandler() {
return new CustomAuthHandler();
}
/*#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/edu/assets/**");
}
*/
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/edu/**","/Login**","/UserSignUP","/organization**","/email_availablity").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/Login").usernameParameter("username").passwordParameter("password")
.defaultSuccessUrl("/index1",true).failureHandler(customAuthenticationHandler).permitAll()
.and()
.logout()
.permitAll()
.and()
.csrf().disable();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
}
}
Finally i found a answer...
1.Create an interceptor class in your spring boot
public class MyCustomInterceptor implements HandlerInterceptor{
//unimplemented methods comes here. Define the following method so that it
//will handle the request before it is passed to the controller.
#Override
public boolean preHandle(HttpServletRequest request,HttpServletResponse response){
//your custom logic here.(for request validating)
return true;
}
}
2.Define a configuration class
#Configuration
public class MyConfig extends WebMvcConfigurerAdapter{
#Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(new MyCustomInterceptor()).addPathPatterns("/**");
}
}
thats all now your requests will pass through the logic defined under preHandle() method of MyCustomInterceptor before pass through controller
Firstly, you are validating api not .jsp pages.
#Configuration annotation is used in java file to configure your application instead of xml configuration. From the spring's official doc:
Indicates that a class declares one or more #Bean methods and may be
processed by the Spring container to generate bean definitions and
service requests for those beans at runtime.
So, When you annotated a class with #Configuration the methods are executed just for a time at the very beginning when you start your program. That's why you can't dynamically check UserRoles from the below method:
#Override
protected void configure(HttpSecurity http) throws Exception {
// inner codes
}
So, you have to hard coded(in your .java file or .properties file) if you want to authorize from that method.
The Solution
I have noticed that you are using UserDetailsService for the login purpose. You can modify the loadUserByUsername(String username) method and dynamically check authorization for users.
Steps:
Store the complete user information: login, password and Roles in a database. For this you need 3 Tables: 1. User, 2. Roles and 3. User_Roles. Each time while login, you need to set the UserRoles(roles are read from database) to the UserInfo object.
Then you can add a Interceptor(as a url filter) to your project to check each api(or url) whether Logged user is authorized for the api or not. For this, you can query database with the username for the roles and compare with the requested uri.