原文地址:http://websystique.com/spring-security/spring-security-4-method-security-using-preauthorize-postauthorize-secured-el/

In order to enable Spring Method level Security, we need to annotate a @Configuration class with @EnableGlobalMethodSecurity, as shown below:

package com.websystique.springsecurity.configuration;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
 
     
    @Autowired
    public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("bill").password("abc123").roles("USER");
        auth.inMemoryAuthentication().withUser("admin").password("root123").roles("ADMIN");
        auth.inMemoryAuthentication().withUser("dba").password("root123").roles("ADMIN","DBA");
    }
     
    @Override
    protected void configure(HttpSecurity http) throws Exception {
       
      http.authorizeRequests()
        .antMatchers("/", "/home").access("hasRole('USER') or hasRole('ADMIN') or hasRole('DBA')")
        .and().formLogin().loginPage("/login")
        .usernameParameter("ssoId").passwordParameter("password")
        .and().exceptionHandling().accessDeniedPage("/Access_Denied");
    }
}

@EnableGlobalMethodSecurity enables Spring Security global method security similar to the in XML configurations like shown below:

      
    <http auto-config="true" >
        <intercept-url pattern="/"     access="hasRole('USER') or hasRole('ADMIN') and hasRole('DBA')" />
        <intercept-url pattern="/home" access="hasRole('USER') or hasRole('ADMIN') and hasRole('DBA')" />
        <form-login  login-page="/login"
                     username-parameter="ssoId"
                     password-parameter="password"
                     authentication-failure-url="/Access_Denied" />
    </http>
     
    <global-method-security pre-post-annotations="enabled"/>
 
    <authentication-manager >
        <authentication-provider>
            <user-service>
                <user name="bill"  password="abc123"  authorities="ROLE_USER" />
                <user name="admin" password="root123" authorities="ROLE_ADMIN" />
                <user name="dba"   password="root123" authorities="ROLE_ADMIN,ROLE_DBA" />
            </user-service>
        </authentication-provider>
    </authentication-manager>
     
</beans:beans>

Note that @EnableGlobalMethodSecurity can take several arguments, some are shown below:

  • prePostEnabled :Determines if Spring Security’s pre post annotations [@PreAuthorize,@PostAuthorize,..] should be enabled.
  • secureEnabled : Determines if Spring Security’s secured annotation [@Secured] should be enabled.
  • jsr250Enabled : Determines if JSR-250 annotations [@RolesAllowed..] should be enabled.

You can enable more than one type of annotation in the same application, but only one type should be used for any interface or class as the behavior will not be well-defined otherwise. If two annotations are found which apply to a particular method, then only one of them will be applied.

We will explore first two of above mentioned in detail.

@Secured

@Secured annotation is used to define a list of security configuration attributes for business methods. You can specify the security requirements[roles/permission etc] on a method using @Secured, and than only the user with those roles/permissions can invoke that method. If anyone tries to invoke a method and does not possess the required roles/permissions, an AccessDenied exception will be thrown.

@Secured is coming from previous versions of Spring. It has a limitation that it does not support Spring EL expressions. Consider following example:

package com.websystique.springsecurity.service;
 
import org.springframework.security.access.annotation.Secured;
 
 
public interface UserService {
 
    List<User> findAllUsers();
 
    @Secured("ROLE_ADMIN")
    void updateUser(User user);
 
    @Secured({ "ROLE_DBA", "ROLE_ADMIN" })
    void deleteUser();
     
}

In above example, updateUser method can be invoked by someone with ADMIN role while deleteUser can be invoked by anyone with DBA OR ADMIN role. If anyone tries to invoke a method and does not possess the required role, an AccessDenied exception will be thrown.

What if you want to specify an ‘AND’ condition. I mean , you want deleteUser method to be invoked by a user who have both ADMIN & DBA role. This is not possible straight-away with @Secured annotation.

This can however be done using Spring’s new @PreAuthorize/@PostAuthorize annotations which supports Spring EL, that means possibilities are unlimited.

@PreAuthorize / @PostAuthorize

Spring’s @PreAuthorize/@PostAuthorize annotations are preferred way for applying method-level security, and supports Spring Expression Language out of the box, and provide expression-based access control.

@PreAuthorize is suitable for verifying authorization before entering into method. @PreAuthorize can take into account, the roles/permissions of logged-in User, argument passed to the method etc.

@PostAuthorize , not often used though, checks for authorization after method have been executed, so it is suitable for verifying authorization on returned values. Spring EL provides returnObject object that can be accessed in expression language and reflects the actual object returned from method.

Please refer to Common Built-In Expressions to get the complete list of supported expressions.

Let’s get back to our example, this time using @PreAuthorize / @PostAuthorize.

package com.websystique.springsecurity.service;
 
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PreAuthorize;
 
import com.websystique.springsecurity.model.User;
 
 
public interface UserService {
 
    List<User> findAllUsers();
 
    @PostAuthorize ("returnObject.type == authentication.name")
    User findById(int id);
 
    @PreAuthorize("hasRole('ADMIN')")
    void updateUser(User user);
     
    @PreAuthorize("hasRole('ADMIN') AND hasRole('DBA')")
    void deleteUser(int id);
 
}

Since @PreAuthorize can use Spring Expression Language, any condition can easily be expressed using EL. Method deleteUser is now configured to be invoked by a user who have both ADMIN & DBA roles.

Additionally, we have added a method findById() with @PostAuthorize annotation. With @PostAuthorize, the returned value from the method(User object) will be accessible with returnObject in Spring Expression Language, and individual properties of return user object can be used to apply some security rules. In this example we are making sure that a logged-in user can only get it’s own User type object.

That’s all about basic usage of @Secured, @PreAuthorize, @PostAuthorize and EL.

Below mentioned is service implementation, User model class & Controller, used in this example.

package com.websystique.springsecurity.service;
 
 
import java.util.ArrayList;
import java.util.List;
 
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
 
import com.websystique.springsecurity.model.User;
 
@Service("userService")
@Transactional
public class UserServiceImpl implements UserService{
 
    static List<User> users = new ArrayList<User>();
     
    static{
        users = populateUser();
    }
     
    public List<User> findAllUsers(){
        return users;
    }
     
    public User findById(int id){
        for(User u : users){
            if(u.getId()==id){
                return u;
            }
        }
        return null;
    }
     
    public void updateUser(User user) {
        System.out.println("Only an Admin can Update a User");
        User u = findById(user.getId());
        users.remove(u);
        u.setFirstName(user.getFirstName());
        u.setLastName(user.getLastName());
        u.setType(user.getType());
        users.add(u);
    }
     
    public void deleteUser(int id){
        User u = findById(id);
        users.remove(u);
    }
     
    private static List<User> populateUser(){
        List<User> users = new ArrayList<User>();
        users.add(new User(1,"Sam","Disilva","admin"));
        users.add(new User(2,"Kevin","Brayn","admin"));
        users.add(new User(3,"Nina","Conor","dba"));
        users.add(new User(4,"Tito","Menz","dba"));
        return users;
    }
 
}
public class User {
 
    private int id;
     
    private String firstName;
     
    private String lastName;
     
    private String type;
 
//getters/setters
}
package com.websystique.springsecurity.controller;
 
import java.util.List;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
 
import com.websystique.springsecurity.model.User;
import com.websystique.springsecurity.service.UserService;
 
@Controller
public class HelloWorldController {
 
    @Autowired
    UserService service;
     
    @RequestMapping(value = { "/", "/list" }, method = RequestMethod.GET)
    public String listAllUsers(ModelMap model) {
  
        List<User> users = service.findAllUsers();
        model.addAttribute("users", users);
        return "allusers";
    }
     
    @RequestMapping(value = { "/edit-user-{id}" }, method = RequestMethod.GET)
    public String editUser(@PathVariable int id, ModelMap model) {
        User user  = service.findById(id);
        model.addAttribute("user", user);
        model.addAttribute("edit", true);
        return "registration";
    }
     
    @RequestMapping(value = { "/edit-user-{id}" }, method = RequestMethod.POST)
    public String updateUser(User user, ModelMap model, @PathVariable int id) {
        service.updateUser(user);
        model.addAttribute("success", "User " + user.getFirstName()  + " updated successfully");
        return "success";
    }
     
    @RequestMapping(value = { "/delete-user-{id}" }, method = RequestMethod.GET)
    public String deleteUser(@PathVariable int id) {
        service.deleteUser(id);
        return "redirect:/list";
    }
     
    @RequestMapping(value = "/Access_Denied", method = RequestMethod.GET)
    public String accessDeniedPage(ModelMap model) {
        model.addAttribute("user", getPrincipal());
        return "accessDenied";
    }
 
    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public String loginPage() {
        return "login";
    }
 
    @RequestMapping(value="/logout", method = RequestMethod.GET)
    public String logoutPage (HttpServletRequest request, HttpServletResponse response) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth != null){   
            new SecurityContextLogoutHandler().logout(request, response, auth);
        }
        return "redirect:/login?logout";
    }
 
    private String getPrincipal(){
        String userName = null;
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
 
        if (principal instanceof UserDetails) {
            userName = ((UserDetails)principal).getUsername();
        } else {
            userName = principal.toString();
        }
        return userName;
    }
 
}

Complete code for this example is attached at the end of this post.

Deploy & Run

Download complete code example attached at the end of post. Deploy it on Servlet 3.0 container(Tomcat 8.0.21 e.g.).
Open browser and goto http://localhost:8080/SpringSecurityMethodLevelSecurityAnnotationExample/, you will be prompted for login. Fill in USER role credentials.

Submit, you will see list of users.

Now try to edit or delete a users, you should see accessDenied page, because USER role does not have access to these functions.

Logout. Login with ADMIN role credentials.

Submit, you will see list of users.

Now click on edit for first row [with type='admin']. Edit page should be presented.

Now go back to list of item and click on third row [with type = 'dba']

You got accessDenied because during edit, function findById gets called which is annotated with @PostAuthorize annotation with EL restricting the returned object to be only with type['dba'] same as logged-in user name.

Now click on any delete row, accessDenied should be shown as deleteUser is only allowed for role ‘DBA’.

Now logout, login with DBA role [dba,root123], and click on delete link of first row. Row should be deleted successfully.

That’s it.

Spring Security 4 Method security using @PreAuthorize,@PostAuthorize, @Secured, EL--转的更多相关文章

  1. Spring Security 4 使用@PreAuthorize,@PostAuthorize, @Secured, EL实现方法安全

    [相关已翻译的本系列其他文章,点击分类里面的spring security 4] 上一篇:Spring Security 4 整合Hibernate 实现持久化登录验证(带源码) 原文地址:http: ...

  2. Security注解:@PreAuthorize,@PostAuthorize, @Secured, EL实现方法安全

     说明 (1)JDK版本:1.8(2)Spring Boot 2.0.6(3)Spring Security 5.0.9(4)Spring Data JPA 2.0.11.RELEASE(5)hibe ...

  3. spring security method security

    参考 Spring Security 官方文档 http://www.concretepage.com/spring/spring-security/preauthorize-postauthoriz ...

  4. Java Spring Boot VS .NetCore (九) Spring Security vs .NetCore Security

    Java Spring Boot VS .NetCore (一)来一个简单的 Hello World Java Spring Boot VS .NetCore (二)实现一个过滤器Filter Jav ...

  5. java.security.ProviderException: java.security.KeyException

    本机部署没问题,部署到linux服务器报错: javax.net.ssl.SSLException: java.security.ProviderException: java.security.Ke ...

  6. 记CM+kerberos环境停电后无法启动报错An error: (java.security.PrivilegedActionException: javax.security.sasl.SaslException: GSS initiate failed [Caused by GSSException: No valid credentials provided (Mechanism leve

    公司突然停电,然后cm环境无法重启,报错 An error: (java.security.PrivilegedActionException: javax.security.sasl.SaslExc ...

  7. Spring Security(二十二):6.4 Method Security

    From version 2.0 onwards Spring Security has improved support substantially for adding security to y ...

  8. Spring Security(十七):5.8 Method Security

    From version 2.0 onwards Spring Security has improved support substantially for adding security to y ...

  9. 41.4 Method Security方法安全性

    41.4.1 <global-method-security> 这个元素是为Spring Security beans上的安全方法添加支持的主要手段.可以通过使用注释(在接口或类级别定义) ...

随机推荐

  1. 一线 | 中国联通宣布首批5G手机到位

    腾讯<一线> 作者郭晓峰 据中国联通相关人士今日透露,中国联通用于 5G 友好体验的首批合作 5G 手机全部到位.有 12 个品牌共 15 款 5G 手机及 5G CPE,包括.华为. O ...

  2. Ubuntu16.04 lnmp 环境搭建

    Ubuntu16.04 lnmp 环境搭建 nginx 安装 sudo apt-add-repository ppa:nginx/stablesudo apt-add-repository ppa:o ...

  3. python基础4(小数据池,编码,深浅拷贝)

    1.==与is == 比较值是否相等 is比较内存地址是否相同 2.小数据池 为了节省内存,当数据在一个范围里的时候,两个值相同的变量指向的是小数据池里的同一个地址 数字范围:-5 ~ 256 num ...

  4. numpy学习笔记 - numpy常用函数、向量化操作及基本数学统计方法

    # -*- coding: utf-8 -*-"""主要记录代码,相关说明采用注释形势,供日常总结.查阅使用,不定时更新.Created on Fri Aug 24 19 ...

  5. EEPlat PaaS中的多租户数据隔离模式

    EEPlat PaaS支持三种租户的数据隔离技术:Sparce Column.tenantId字段隔离.每一个租户独立数据库. 1)Sparce Column,和Salesforce Appforce ...

  6. CCEditBox/CCEditBoxImplAndroid

    #ifndef __CCEDITBOXIMPLANDROID_H__ #define __CCEDITBOXIMPLANDROID_H__ #include "cocos2d.h" ...

  7. java9新特性-6-多版本兼容jar包

    1.官方Feature 238: Multi-Release JAR Files 2.使用说明 当一个新版本的Java出现的时候,你的库用户要花费数年时间才会切换到这个新的版本.这就意味着库得去向后兼 ...

  8. 【DNN 系列】 添加模块后不显示

    添加模块后不显示分为几个原因 1.检查.dnn文件是否填写正确,要和对应的页面文件对应上 我有一步是这这个名称地方我填上了 就不显示了.这里需要注意,VIEW 的名城是不需要写的 2.重写文件 实体操 ...

  9. AngularJS 导航栏动态添加.active

    在传统jQuery中,实现导航栏动态添加.active类的思路比较简单,就是当点击的时候,清除其他.active,然后给当前类加上.active. 但是在AngularJS中,就不能再采用这种jQue ...

  10. Ubuntu14.04下tensorflow安装

    自己电脑没装双系统,于是决定在虚拟机里装个tensorflow,以下是安装过程: 1.安装anaconda2 for Linux 官网下的话很慢,去清华的镜像网站下吧,我上一篇文章有网址 安装:bas ...