Spring Security实现动态权限管理

我所理解的动态权限就是RBAC(Role-Based Access Control)。
就是可以自定义角色,配置角色可以访问哪些URL。然后给不同的角色设置不同的角色。

为什么用Spring Security?听说Spring Security是基于Shiro的。Shiro没用过。之所以用Spring Security是因为它安全。废话!是因为可以帮你防御csrf等攻击。其实现在的Chrome浏览器的同源策略已经很不错了,想csrf也没那么容易。Spring Security能防住多少我也不知道,总之比什么都不做好。XSS的话大家做好过滤就好了。比如Spring MVC就可以过滤掉一些,页面用<c:out/> 标签输出。跑题了啊,其实我也就是想装个X而已。

参考这里 How to create custom methods for use in spring security expression language annotations

一直想用Spring Security实现动态权限管理,这回搞定了,总算了结了一个心愿。
废话不多说,假设你已经会用Spring Security了。如果您不会,请多看官方文档。Spring的文档写的相当到位,就怕你没耐心看。比如你用过Spring MVC,而且已经有一个Spring MVC的项目了。现在你想在你的Spring MVC项目中集成Spring Security怎么办?看这个tutorial:Hello Spring MVC Security Java Config
只想啰嗦一句。之前Spring MVC的配置文件在getServletConfigClasses这个方法里,现在要挪到getRootConfigClasses方法里。至于为什么,文档里说了,下面的注释里也有,注释都来自官方文档。

/**
 * it’s even the recommended way to set up your Spring MVC application
 */
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[] { WebConfig.class };
        // The @ComponentScan is loading all configuration within the same package (and child packages) as RootConfiguration. Since SecurityConfig is in this package, it will be loaded with our existing setup and there is nothing more to do.
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return null;
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }

}

其实很简单。自定义一个类。在类里加一个方法:

@Component("mySecurityService")
public class MySecurityService {
    public boolean hasPermission(Authentication authentication, Object foo) {
        System.out.println("来了");
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        Iterator<? extends GrantedAuthority> iterator = authorities.iterator();
        while(iterator.hasNext()){
            GrantedAuthority ga = iterator.next();
            if("ROLE_ADMIN".equals(ga.getAuthority())){
                return true;
            }
        }
        return false;
    }
}

然后在要使用的地方加一个@PreAuthorize("@mySecurityService.hasPermission(authentication, #model)") 就OK了。

    @PreAuthorize("@mySecurityService.hasPermission(authentication, #model)")
    @RequestMapping(value = { "/testMethod" }, method = RequestMethod.GET)
    public String testMethodSecurity(ModelMap model) {
        model.addAttribute("greeting", "恭喜你。此页面必须同时拥有admin权限才能访问哦");
        return "welcome";
    }

注意这里的authentication.getAuthorities();别小看这个,这可是大有来头!

Spring Security总共分为两大块:anthentication和authorization。
刚开始学Spring Security的时候这两个地方你都会卡住!为什么?

Spring Security有一个默认的anthentication。它会帮你生成一个登录用的form表单。蛋疼的是这个form表单只有username和password两个参数。问题来了,我想加个验证码怎么办? 不只这样,默认的authentication会自动帮你注入硬编码的角色。就是你在配置文件里配置的ROLE_USER,ROLE_ADMIN等等。我想要自定义角色,我想要动态角色怎么办?卡住了不是?

如果你使用默认的authentication那么此时authentication.getAuthorities();拿到的角色就是固定的(你在配置文件里配置的)。

authorization你会卡在什么地方?文档里举的例子是@PreAuthorize("hasRole('ADMIN') AND hasRole('DBA')") 看到了吧,这里的ROLE就是固定的,而不是动态的。

所以你需要的是@PreAuthorize(“canIAccess(authorities)”)。即自定义一个decide方法,并且把动态角色传给这个decide方法!

怎么做?

登录(authenticate)的时候想带一个验证码,我们需要自定义一个form表单。 想要动态角色就要在登录成功的时候把用户的角色信息从db中查出来,把它注入到Spring的某个类中,具体哪个类我忘了,文档里有提到,你得仔细看文档“Architecture and Implementation”这一节。 然后在decide方法里拿到之前注入的角色信息。

说起来容易,做起来可没那么容易!

比如说你的验证码参数,在认证(authenticate)的时候如何获取?你可能会说,先把captcha存到session里。只要在认证的时候拿到request对象不就行了吗?关键是你拿的到吗?!

想在认证的时候拿到request太不容易了:

首先得在BeanPostProcessor里注册我们自定义的认证源:MyWebAuthenticationDetailsSource

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessAfterInitialization(Object bean, String name) throws BeansException {
        if (bean instanceof UsernamePasswordAuthenticationFilter) {
            MyWebAuthenticationDetailsSource myWebAuthenticationDetailsSource = new MyWebAuthenticationDetailsSource();
            ((UsernamePasswordAuthenticationFilter) bean)
                    .setAuthenticationDetailsSource(myWebAuthenticationDetailsSource);
        }
        return bean;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException {
//        System.out.println(bean.getClass());
        return bean;
    }
}

在MyWebAuthenticationDetailsSource的buildDetails方法里返回一个自定义的详细认证类MyWebAuthenticationDetails

public class MyWebAuthenticationDetailsSource extends WebAuthenticationDetailsSource {

    @Override
    public WebAuthenticationDetails buildDetails(HttpServletRequest context) {
        return new MyWebAuthenticationDetails(context);
    }

}

在详细认证类里让Spring自动帮我们注入request对象:

public class MyWebAuthenticationDetails extends WebAuthenticationDetails {

    /**
     * serialVersionUID
     */
    private static final long serialVersionUID = 1435615659672216808L;
    private HttpServletRequest request;

    public MyWebAuthenticationDetails(HttpServletRequest request) {
        super(request);
        this.request = request;
    }

    public HttpServletRequest getRequest() {
        return request;
    }

    public void setRequest(HttpServletRequest request) {
        this.request = request;
    }

}

最后在我们自定义的认证里拿到MyWebAuthenticationDetails,从而拿到request

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
    @Autowired
    private CustomUserService userService;

    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        MyWebAuthenticationDetails myDetails = (MyWebAuthenticationDetails) authentication.getDetails();
        HttpServletRequest request = myDetails.getRequest();
        String captcha = request.getParameter("captcha");
        HttpSession session = request.getSession();
        String realCaptcha = (String) session.getAttribute("captcha");
        if(!realCaptcha.equals(captcha)){
            throw new BadCredentialsException("验证码错误");
        }
        String username = authentication.getName();
        String password = (String) authentication.getCredentials();

        CustomUser user = userService.loadUserByUsername(username);

        // http://docs.spring.io/spring-security/site/faq/faq.html#faq-extra-login-fields
        if (user == null || !user.getUsername().equalsIgnoreCase(username) || !password.equals(user.getPassword())) {
            throw new BadCredentialsException("用户名或密码错误");
        }
        Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
        return new UsernamePasswordAuthenticationToken(user, password, authorities);
    }

    public boolean supports(Class<?> arg0) {
        return true;
    }
}

注意这个CustomUserService是我们自定义的UserDetailsService。就是通过这个类把用户信息从db里查出来。为了方便我就直接在service里写死了,懒得定义dao,从db里查了。

@Service
public class CustomUserService implements UserDetailsService{
//    @Autowired
//    private UserDAOImpl userDao;

   public CustomUser loadUserByUsername(String username) throws UsernameNotFoundException {
//       return userDao.loadUserByUsername(username);
       CustomUser user = new CustomUser();
       if("bill".equals(username)){
           user.setFirstName("kb");
           user.setLastName("gc");
           user.setUsername("bill");
           user.setPassword("ff9830c42660c1dd1942844f8069b74a");
           CustomRole userRole = new CustomRole();
           userRole.setName("ROLE_USER");
           List<CustomRole> roles = new ArrayList<CustomRole>();
           roles.add(userRole);
           user.setAuthorities(roles);
       } else if("admin".equals(username)){
           user.setFirstName("kb2");
           user.setLastName("gc2");
           user.setUsername("admin");
           user.setPassword("ff9830c42660c1dd1942844f8069b74a");
           List<CustomRole> roles = new ArrayList<CustomRole>();
           CustomRole userRole = new CustomRole();
           userRole.setName("ROLE_USER");
           roles.add(userRole);
           CustomRole adminRole = new CustomRole();
           adminRole.setName("ROLE_ADMIN");
           roles.add(adminRole);
           user.setAuthorities(roles);
       } else if("dba".equals(username)){
           user.setFirstName("kb3");
           user.setLastName("gc3");
           user.setUsername("dba");
           user.setPassword("ff9830c42660c1dd1942844f8069b74a");
           List<CustomRole> roles = new ArrayList<CustomRole>();
           CustomRole userRole = new CustomRole();
           userRole.setName("ROLE_USER");
           roles.add(userRole);
           CustomRole adminRole = new CustomRole();
           adminRole.setName("ROLE_ADMIN");
           roles.add(adminRole);
           CustomRole dbaRole = new CustomRole();
           dbaRole.setName("ROLE_DBA");
           roles.add(dbaRole);
           user.setAuthorities(roles);
       } else{
           user = null;
       }
       return user;
   }
}

CustomRole也是我们自定义的ROLE。这样你可以在自定义的ROLE加其他字段,这里我只定义一个了name字段:

public class CustomRole implements GrantedAuthority{
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAuthority() {
        return this.name;
    }
}

自定义认证就在CustomAuthenticationProvider的authenticate方法里完成。前面做了那么多工作就为了区区一个request。这里有一句maimapi不知当讲不当讲。

在authenticate方法的最后return new UsernamePasswordAuthenticationToken(user, password, authorities); 就把authorities注入到Spring的某个类里了。 所以在MySecurityService的hasPermission方法里我们可以拿到authentication,进而拿到authorities:

@Component("mySecurityService")
public class MySecurityService {
    public boolean hasPermission(Authentication authentication, Object foo) {
        System.out.println("来了");
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        Iterator<? extends GrantedAuthority> iterator = authorities.iterator();
        while(iterator.hasNext()){
            GrantedAuthority ga = iterator.next();
            if("ROLE_ADMIN".equals(ga.getAuthority())){
                return true;
            }
        }
        return false;
    }
}

曲折啊,你说这句maimapi当讲不当讲?

时间: 2024-09-17 04:16:28

Spring Security实现动态权限管理的相关文章

Spring security实现登陆和权限角色控制_mssql2008

 随笔简介 1.spring版本:4.3.2.RELEASE+spring security 版本:4.1.2.RELEASE(其它不做说明) 2.所展示内容全部用注解配置 3.springmvc已经配置好,不作说明 4.会涉及到springmvc,spel,el的东西,不熟悉的同学可以先去看一下这方面内容,特别是springmvc  首先想一下,登陆需要什么,最简单的情况下,用户名,密码,然后比对数据库,如果吻合就跳转到个人页面,否则回到登陆页面,并且提示用户名密码错误.这个过程中应该还带有权

Android权限管理原理(含6.0)

前言 Android系统在MarshMallow之前,权限都是在安装的时候授予的,虽然在4.3时,Google就试图在源码里面引入AppOpsManager来达到动态控制权限的目的,但由于不太成熟,在Release版本中都是把这个功能给隐藏掉的.在6.0之后,Google为了简化安装流程且方便用户控制权限,正式引入了runtime-permission,允许用户在运行的时候动态控制权限.对于开发而言就是将targetSdkVersion设置为23,并且在相应的时机动态申请权限,在适配了Andro

Spring Security 2 中动态角色权限的实现

安全框架的主体包括两部分即验权和授权.Spring Security2可以很好的实 现这两个过程.Spring Security2对其前身acegi最大的改进是提供了自定义的 配置标签,通过Security的命名空间定义了http和authentication-provider等 标签,这样做的好处是极大地简化了框架的配置,并很好地隐藏了框架实现的细 节,在配置的表述上也更清晰,总体上提高了框架的易用性. 然而,该框架默认的权限配置方式在xml中,又因为新版本隐藏了实现细节, 在动态权限的扩展上

使用springmvc+spring+hibernate实现权限管理

问题描述 以前做权限管理的时候是建立角色权限表,然后通过struts自带的jsp标签来显示有权限功能的模块,现在使用springmvc,不知道该怎么选择性的显示模块了?有人有经验吗 解决方案 解决方案二:一样也建个权限表吧,你在页面用js判断就是了解决方案三:你说的通过struts标签指的是循环迭代标签在jsp页面中显示?这不冲突啊..你使用springMVC一样可以解决方案四:引用2楼hersing的回复: 你说的通过struts标签指的是循环迭代标签在jsp页面中显示?这不冲突啊..你使用s

Spring Security 2配置精讲 上

安全权限管理手册 http://www.family168.com/oa/springsecurity/html/ 众所周知,Spring Security针对Acegi的一个重大的改进就在于其配置方式大大简化了.所以如果配置还是基于Acegi-1.X这样比较繁琐的配置方式的话,那么我们还不如直接使用Acegi而不要去升级了.所以在这里,我将结合一个示例,重点讨论一下Spring Security 2是如何进行配置简化的. 搭建基础环境 首先我们为示例搭建基本的开发环境,环境的搭建方式,可以参考

关于权限管理设计文章整理,希望对大家有所帮助

关于权限管理文章整理 1. AppBox v2.0中的权限实现 http://www.cnblogs.com/sanshi/p/3274824.html  2..NET通用权限系统快速开发框架 http://blog.csdn.net/shecixiong/article/details/10574967 3. 通用权限管理 http://blog.csdn.net/xiyang_1990/article/details/9768385 4. ASP.NET MVC+EF框架+EasyUI实现权

spring security启动异常,求大神来指点下

问题描述 SSH想加上spring security,搞了几天都没搞出来,哪位大神来指点下  WEB.XML配置 <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation<

Spring Boot中集成Spring Security 专题

if语句中条件判断就是检查当前的url请求是否是logout-url的配置值,接下来,获取用户的authentication,并循环调用处理器链中各个处理器的logout()函数,前面在parse阶段说过,处理器链中有两个实例,处理会话的SecurityContextLogoutHandler及remember-me服务,我们来一一看看它们的logout函数实现: 2.1.0 SecurityContextLogoutHandler public void logout(HttpServletR

Spring Security 入门详解(转)

1.Spring Security介绍 Spring Security是基于spring的应用程序提供声明式安全保护的安全性框架,它提供了完整的安全性解决方案,能够在web请求级别和方法调用级别 处理身份证验证和授权.它充分使用了依赖注入和面向切面的技术. Spring security主要是从两个方面解决安全性问题: web请求级别:使用servlet过滤器保护web请求并限制URL级别的访问 方法调用级别:使用Spring AOP保护方法调用,确保具有适当权限的用户采用访问安全保护的方法.