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.
Related
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.
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'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
I am looking for a non invasive way to add a captcha filter for certain api calls.
My setup consists of two WebSecurityConfigurerAdapters with one filter each (not the captcha filter):
Internal api ("/iapi" use Filter A on all calls but also ignore some public requests like /authenticate)
External api ("/eapi" use Filter B on all calls)
How can I add a filter before the Spring Security stuff, on public, internal api or external api calls? I don't need the SecurityContext, just need to check for a Captcha in the request headers, forward to filterChain (normal filters) or manually deny access. I tried declaring a filter in web.xml, but that breaks the ability to use dependency injection.
Here is my Spring Security Configuration:
#EnableWebSecurity
public class SpringSecurityConfig {
#Configuration
#Order(1)
#EnableGlobalMethodSecurity(securedEnabled = true)
public static class InternalApiConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Autowired
private Filter filterA;
public InternalApiConfigurerAdapter() {
super(true);
}
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/public/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/iapi/**")
.exceptionHandling().and()
.anonymous().and()
.servletApi().and()
.authorizeRequests()
.anyRequest().authenticated().and()
.addFilterBefore(filterA, (Class<? extends Filter>) UsernamePasswordAuthenticationFilter.class);
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return authenticationManager();
}
}
#Configuration
#Order(2)
#EnableGlobalMethodSecurity(securedEnabled = true)
public static class ExternalApiConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Autowired
private FilterB filterB;
public ExternalApiConfigurerAdapter() {
super(true);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/external/**")
.exceptionHandling().and()
.anonymous().and()
.servletApi().and()
.authorizeRequests()
.anyRequest().authenticated().and()
.addFilterBefore(filterB, (Class<? extends Filter>) UsernamePasswordAuthenticationFilter.class);
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return authenticationManager();
}
}
Update: At the moment I have a working configuration with a filter declared in web.xml. However, it has the drawback of being seperated from the Spring Context (e.g. no autowiring of beans), so I am looking for a better solution leveraging Spring.
Summary: There are two remaining problems:
add a filter for specific urls only - using beforeFilter(...) inside any configuration adds a filter to all urls of that configuration. Antmatchers didn't work. I need something like that: /iapi/captcha/, /external/captcha/, /public/captcha/*.
I have a public api which bypasses Spring Security completely: (web
.ignoring()
.antMatchers("/public/**");). I need to bypass Spring Security but still declare a filter there, using Spring autowiring but not necessarily Spring Security features, since my captcha filter only rejects or forwards calls in a stateless way.
You already have a working configuration with filters A and B inserted before UsernamePasswordAuthenticationFilter so should be easy to add another custom filter.
First, you create the filter, and declare it as a bean, either annotating the class with #Component, or as a #Bean inside a #Configuration class, so it is ready to be injected with #Autowired.
Now you are able to inject it, as filter A and B, and use it. According to the Filter Ordering section in the Spring Security reference documentation, the very first Filter in the chain is ChannelProcessingFilter, so in order to insert the filter before anything else in the Spring Security filter chain, you'd do this:
#Autowired
private CaptchaFilter captchaFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/iapi/**")
.addFilterBefore(captchaFilter, (Class<? extends Filter>) ChannelProcessingFilter.class)
.addFilterBefore(filterA, (Class<? extends Filter>) UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.anyRequest().authenticated();
}
By the way, exceptionHandling() anonymous() and servletApi() aren't needed because when extending WebSecurityConfigurerAdapter, these are already included, except for anonymous() when you actually specify more configuration details, as it states HttpSecurity javadoc
Keep in mind that the Spring Security "entrypoint", the DelegatingFilterProxy still will be executed before your filter, but this component only delegates the request to the first filter in the chain, which in this case would be the CaptchaFilter, so you really would execute your filter before anything else from Spring Security.
But if you still want the captcha filter be executed before the DelegatingFilterProxy, there is no way to do so in the Spring Security configuration, and you need to declare it in the web.xml file.
Update: If you do not desire to include the captcha filter in the other configurations, you can always add a third configuration, and the configurations class would be as follows:
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SpringSecurityConfig {
#Configuration
#Order(1)
public static class CaptchaApiConfigurerAdatper extends WebSecurityConfigurerAdapter {
#Autowired
private CaptchaFilter captchaFilter;
public CaptchaApiConfigurerAdatper() {
super(true);
}
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/public/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatcher("/iapi/captcha**")
.antMatcher("/external/captcha**")
.and()
.addFilterBefore(captchaFilter, (Class<? extends Filter>) ChannelProcessingFilter.class)
.authorizeRequests()
.anyRequest().authenticated();
}
}
#Configuration
#Order(2)
public static class InternalApiConfigurerAdapter extends WebSecurityConfigurerAdapter {
// ommiting code for the sake of clarity
}
#Configuration
#Order(3)
public static class ExternalApiConfigurerAdapter extends WebSecurityConfigurerAdapter {
// ommiting code for the sake of clarity
}
By the way, another tip, you can refactor all the common configuration outside the specific configurations, into the main class, like #EnableGlobalMethodSecurity(securedEnabled = true) the AuthenticationManager, the WebSecurity to skip security for the public, but for those since the main class is not extending anything you should #Autowire the method declarations.
Although there would be one problem with the WebSecurity, if you are ignoring /public/** the matcher for the HttpSecurity with /public/captcha** would be ignored, so i guess, you shouldnt refactor out the WebSecurity and have a different pattern in the CaptchaConfig class so it doesnt overlap.
I am using Spring Security 3.2.3.RELEASE
And here is code of my WebSecurityConfigurerAdapter
#Configuration
#EnableWebSecurity
public class WebSecurityContext extends WebSecurityConfigurerAdapter {
private static final Logger log = LogManager.getLogger(WebSecurityContext.class);
#Autowired
private AuthenticationProvider authenticationProvider;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
log.info("Setting AuthenticationManagerBuilder");
auth.authenticationProvider(authenticationProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
log.info("Configuring HttpSecurity");
http
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/**").hasRole("USER")
.anyRequest().authenticated()
.and()
.httpBasic();
}
}
And result is: HTTP Status 403 - Access is denied
But if I comment this line:
//.antMatchers("/**").hasRole("USER")
Everything works fine. Which means (I guess) something wrong with my roles or HttpSecurity.
So I started debugging. I double check that my UserDetails have two GrantedAuthorities with names: ADMIN, USER.
So any ideas what could cause the problem?
From javadoc of the method hasRole(String role):
Shortcut for specifying URLs require a particular role. If you do not
want to have "ROLE_" automatically inserted see hasAuthority(String).
So you can use hasAuthority or hasAnyAuthority instead
Answering my own question
Apparently Spring Security automatically adds prefix ROLE_ to each role name. So adding ROLE_ prefix to each role name in my DB solved the problem.