====== Spring Security ====== The spring-security is a project, which solves Authentication/Authorisation, protection against common attacks and many other security concerns. It nicely integrates with any Spring application (grails, spring-boot). You can check [[https://projects.spring.io/spring-security/|https://projects.spring.io/spring-security/]] for more information. ===== Use in Errigal projects ===== At the time of writing this, the most 'advanced' example of using spring-security was [[https://bitbucket.org/errigal/content-distributor|Content Distributor]]. It leverages spring-security to implement authentication (configurable either for CAS or basic-auth) [[http://www.baeldung.com/spring-security-authentication-with-a-database|backed by a DB table]]. Using this provides currently logged in user context to each Controller/Service. [[http://www.baeldung.com/spring-security-expressions-basic|Annotations]] are used in order to authorise the controller operations to particular roles. Filtering of returned entities by user's relation to particular company is also implemented. ===== Setting up authentication ===== The authentication (and all the other security things) are set-up in the [[https://bitbucket.org/errigal/content-distributor/src/d413c968df5063db414b069ae1f9bdf8e0726b8d/src/main/groovy/com/errigal/content/distributor/spring/security/SecurityConfiguration.groovy?at=master&fileviewer=file-view-default|SecurityConfiguration.groovy]] file. Setting up the authentication means providing your [[http://www.baeldung.com/spring-security-authentication-provider|AuthenticationProvider]] and configuring it in the SecurityConfiguration just like in following code. The code actually allows switching between CAS authentication and simple local DB based authentication provider based on the configuration (variable //casEnabled//). The passed //authenticationUserDetailsService// is implemented in [[https://bitbucket.org/errigal/content-distributor/src/d413c968df5063db414b069ae1f9bdf8e0726b8d/src/main/groovy/com/errigal/content/distributor/spring/security/RepositoryBasedUserDetailsService.groovy?at=master&fileviewer=file-view-default|RepositoryBasedUserDetailsService.groovy]] and uses DB access to actually look for user by username (or cas authentication token, which contains username). The key point that Spring uses this service in order to get user details (containing the list of his roles). @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { if (casEnabled) { auth.authenticationProvider(casAuthenticationProvider()) } else { auth.authenticationProvider(localAuthenticationProvider()) } } AuthenticationProvider localAuthenticationProvider() { DaoAuthenticationProvider provider = new DaoAuthenticationProvider() provider.setUserDetailsService(authenticationUserDetailsService) provider.setPasswordEncoder(passwordEncoder) return provider } AuthenticationProvider casAuthenticationProvider() { CasAuthenticationProvider provider = new CasAuthenticationProvider() provider.setAuthenticationUserDetailsService(authenticationUserDetailsService) provider.setServiceProperties(serviceProperties()) provider.setTicketValidator(new Cas20ServiceTicketValidator(urlPrefix)) provider.setKey('an_id_for_this_auth_provider_only') return provider } ===== Securing Resources ===== Once the authentication is set-up, we can start enforcing certain authorisations for our code. There are several ways of doing that. ==== Securing URLs ==== This can be done in the already mentioned [[https://bitbucket.org/errigal/content-distributor/src/d413c968df5063db414b069ae1f9bdf8e0726b8d/src/main/groovy/com/errigal/content/distributor/spring/security/SecurityConfiguration.groovy?at=master&fileviewer=file-view-default|SecurityConfiguration.groovy]] in a following way (the key part starts with //http.authorizeRequests()// and allows you to fine tune the access to URL in your web app). The provided @Override protected void configure(HttpSecurity http) throws Exception { if (casEnabled) { http.exceptionHandling() .authenticationEntryPoint(casAuthenticationEntryPoint()).and().addFilter(casAuthenticationFilter()) .addFilterBefore(singleSignOutFilter(), CasAuthenticationFilter.class) .addFilterBefore(requestCasGlobalLogoutFilter(), LogoutFilter.class) } else { http.httpBasic() } http.authorizeRequests().anyRequest().fullyAuthenticated().and().httpBasic() http.logout() .logoutUrl('/logout') .logoutSuccessUrl('/') .invalidateHttpSession(true) .deleteCookies('JSESSIONID') http.csrf().disable() } This sample is pretty simple, but you can use more complex/combined filters as seen bellow (the rules are evaluated in the order they are defined). http.authorizeRequests() .antMatchers("/resources/**", "/signup", "/about").permitAll() .antMatchers("/admin/**").hasRole("ADMIN") .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')") .anyRequest().authenticated() .and() ==== Programatically ==== The most straightforward way is to programatically get the context of the currently logged in user in a following way. The important part is the first 2 lines in the filter method. They get the current logged in user ([[https://bitbucket.org/errigal/content-distributor/src/d413c968df5063db414b069ae1f9bdf8e0726b8d/src/main/groovy/com/errigal/content/distributor/spring/security/UserPrincipal.groovy?at=master&fileviewer=file-view-default|UserPrincipal]]) from the Spring context. The good thing about this is that it can contain some application specific information (e.g. relation of the user to a company), which can then be used to make a decision about restricting access to a resource (in this case returning simply true/false). E.g. this particular code uses the //hasUnlimitedAccess// method, which itself encapsulates logic to determine whether this user is //ADMIN//. @Transactional abstract class CompanyBasedPermission { boolean authorize(T domain) { filter(domain) } boolean filter(T domain) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication() UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal() if (userPrincipal.hasUnlimitedAccess()) { return true } else { Company company = userPrincipal.company if (company == null) { return false } else { return resolveCompanyPermission(domain, company) } } } abstract boolean resolveCompanyPermission(T domain, Company companyFromCurrentUser) } ==== Using Annotations ==== This is a very convenient and declarative way of defining authorisation and is preferred to be used for simple rules. It has to be explicitly enabled in [[https://bitbucket.org/errigal/content-distributor/src/d413c968df5063db414b069ae1f9bdf8e0726b8d/src/main/groovy/com/errigal/content/distributor/spring/security/SecurityConfiguration.groovy?at=master&fileviewer=file-view-default|SecurityConfiguration.groovy]]: @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) class SecurityConfiguration extends WebSecurityConfigurerAdapter { ... Then you can use various annotations as can be seen on following example: @RepositoryRestResource(collectionResourceRel = 'distribution', path = 'distribution') interface DistributionRepository extends PagingAndSortingRepository { Distribution findByName(String name) // have to implement the filtering logic in query as the paging doesn't work with filtering and would be also not nice with large counts of Distributions @Override @Query("select d from Distribution d JOIN d.companies c WHERE 1 = ?#{principal.hasUnlimitedAccess() ? 1 : 0} OR c.code = ?#{principal.company?.code}") Page findAll(Pageable pageable) @Override @PostAuthorize("@distributionPermission.authorize(returnObject)") Distribution findOne(Long aLong) @PostFilter("@distributionPermission.filter(filterObject)") @Query("SELECT d FROM Distribution d JOIN d.companies c WHERE c.code in (:companyCodes)") List findByCompanyCodes(@Param("companyCodes") List companyCodes) @Override @Secured(['ROLE_ADMIN', 'ROLE_DISTRIBUTION_MANAGER', 'ROLE_COMPANY_ADMIN']) def S save(S entity) @Secured(['ROLE_ADMIN', 'ROLE_DISTRIBUTION_MANAGER', 'ROLE_COMPANY_ADMIN']) void delete(Long aLong) } === @Secured === The last method (//delete//) is secured using the [[https://docs.spring.io/spring-security/site/docs/3.2.7.RELEASE/apidocs/org/springframework/security/access/annotation/Secured.html|@Secured]] annotation. In this particular case we want to the delete method to be accessible only to user, who has one of the defined (//ADMIN//) roles. === @PostFilter === The //findByCompanyCodes// method is annotated with [[http://www.baeldung.com/spring-security-prefilter-postfilter|@PostFilter]] annotation, which does filtering on the resulting objects. The filtering is implemented by [[https://bitbucket.org/errigal/content-distributor/src/d413c968df5063db414b069ae1f9bdf8e0726b8d/src/main/groovy/com/errigal/content/distributor/repository/permission/DistributionPermission.groovy?at=master&fileviewer=file-view-default|DistributionPermission]], which is based on the //CompanyBasedPermission// mentioned above - it basically just checks every returned //Distribution//, whether it is related to a //Company// of current user (in case it is not, it is not returned from the method). === @PostAuthorize === The //findOne// method uses the @PostAuthorize annotation. It's behaviour is very similar to @PostFilter, but instead of filtering out the particular objects from list it simply returns back a value or throws security exception. This is intended as if you are asking for an item you are not authorized to get, it's much better to get the Exception than the empty value. === Using user context in @Query === All of the previous methods mean that the data has to be somehow present in the application to authorise it. This might not be very feasible for large data sets or in case you want to use some limit or paging of the data from DB. For these cases the current user's context can be also used in the query to DB. This is done in the //findAll// method, which actually uses the current UserPrincipal in order to do the filtering on the DB level. This is possible due to use of [[http://www.baeldung.com/spring-expression-language|SpEL]], which allows using Spring things in annotations. In the sample the first condition checks the if the user is admin (using the same method as in //CompanyBasedPermission// above) and then checks whether the //Distribution.company// is the same as in current //UserPrincipal//. As we can see we implement exactly the same logic as in [[https://bitbucket.org/errigal/content-distributor/src/d413c968df5063db414b069ae1f9bdf8e0726b8d/src/main/groovy/com/errigal/content/distributor/repository/permission/DistributionPermission.groovy?at=master&fileviewer=file-view-default|DistributionPermission]], which itself isn't good, but there is no way how to express that logic in a single way for both backend and DB. This is a trade-off for making this the best way for large data or paging. === @PreAuthorize / @PreFilter === There are also other annotation, which can be used in a very similar manner, but instead of filtering/authorising the return values, they can filter/authorise the input parameters of a method.