springBoot+springSecurity 动态管理Restful风格权限(三)

首先给大家推荐一下我老师大神的人工智能教学网站。教学不仅零基础,通俗易懂,而且非常风趣幽默,还时不时有内涵黄段子!点这里可以跳转到网站

上一篇博客
springBoot+springSecurity 数据库动态管理用户、角色、权限(二)
只是实现了用户、角色、权限的动态管理,但是其权限管理是有缺陷的,他不支持restful风格的接口权限管理,因为他无法区分客户端的请求方式。

本片博客是为了弥补此缺陷的,本篇博客将在 springBoot+springSecurity 数据库动态管理用户、角色、权限(二)
的基础上进行修改使其支持 restful 风格的接口的权限管理。

本文目录:
1. 分析工作量
2. 修改代码
3. 准备数据
4. 测试


一、分析

  1. 首先分析一下工作量吧,因为要支持 restful 风格的接口,那么我们在判断用户是不是有权限访问的时候不仅要判断 url 还要判断 请求方式。 所以我门需要修改数据库表,因为我门的权限表还没有method 字段。
  2. 由于要判断 url 和 method 所以要在CustomUserService 类的 loadUserByUsername 方法中要添加 权限的 url 和 method 。但是SimpleGrantedAuthority 只支持传入一个参数。 所以我门考虑要再写一个类 实现 GrantedAuthority 接口,并在构造函数中传入两个参数。嘻嘻。
  3. 由于我们不仅要判断url 还要 判断请求方法,所以当然要修改 MyAccessDecisionManager 的decide 方法的内容了。
因为:decide 方法是判定是否拥有权限的决策方法 ,三个参数的含义分别为:
        //authentication 是释CustomUserService中循环添加到 GrantedAuthority 对象中的权限信息集合.

        //object 包含客户端发起的请求的requset信息,可转换为 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();

        //configAttributes 为MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法返回的结果,此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法,用来判定用户是否有此权限。如果不在权限表中则放行。
  1. 当然在 修改一下 MyInvocationSecurityMetadataSourceService 的getAttributes 方法。//此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法,用来判定用户是否有此权限。如果不在权限表中则放行。
    //因为我不想每一次来了请求,都先要匹配一下权限表中的信息是不是包含此url,我准备直接拦截,不管请求的url 是什么都直接拦截,然后在MyAccessDecisionManager的decide 方法中做 拦截还是放行的决策。

5.关闭csrf
6.添加restful 风格的接口

好了分析完了,接下来就是编码了。


二、 修改

1. 修改permission表

添加method 字段,当然Permission 的java bean 中 也要添加此属性和其get set方法。

这里写图片描述
  //请求方法
    private String method;
      public String getMethod() {
        return method;
    }

    public void setMethod(String method) {
        this.method = method;
    }

2. 实现 GrantedAuthority 接口

新建java类MyGrantedAuthority 实现 GrantedAuthority 接口

package com.us.example.service;

import org.springframework.security.core.GrantedAuthority;

/**
 * Created by yangyibo on 17/2/15.
 */
public class MyGrantedAuthority implements GrantedAuthority {

    private String url;
    private String method;

    public String getPermissionUrl() {
        return url;
    }

    public void setPermissionUrl(String permissionUrl) {
        this.url = permissionUrl;
    }

    public String getMethod() {
        return method;
    }

    public void setMethod(String method) {
        this.method = method;
    }

    public MyGrantedAuthority(String url, String method) {
        this.url = url;
        this.method = method;
    }

    @Override
    public String getAuthority() {
        return this.url + ";" + this.method;
    }
}

在CustomUserService 类中使用MyGrantedAuthority

public UserDetails loadUserByUsername(String username) {
        SysUser user = userDao.findByUserName(username);
        if (user != null) {
            List<Permission> permissions = permissionDao.findByAdminUserId(user.getId());
            List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
            for (Permission permission : permissions) {
                if (permission != null && permission.getName() != null) {

                    GrantedAuthority grantedAuthority = new MyGrantedAuthority(permission.getUrl(), permission.getMethod());
                    grantedAuthorities.add(grantedAuthority);
                }
            }
            return new User(user.getUsername(), user.getPassword(), grantedAuthorities);
        } else {
            throw new UsernameNotFoundException("admin: " + username + " do not exist!");
        }
    }

3.修改 MyAccessDecisionManager 的decide 方法

package com.us.example.service;

import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.Iterator;

/**
 * Created by yangyibo on 17/1/19.
 */
@Service
public class MyAccessDecisionManager implements AccessDecisionManager {

    //decide 方法是判定是否拥有权限的决策方法
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {

        HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
        String url, method;
        AntPathRequestMatcher matcher;
        for (GrantedAuthority ga : authentication.getAuthorities()) {
            if (ga instanceof MyGrantedAuthority) {
                MyGrantedAuthority urlGrantedAuthority = (MyGrantedAuthority) ga;
                url = urlGrantedAuthority.getPermissionUrl();
                method = urlGrantedAuthority.getMethod();
                matcher = new AntPathRequestMatcher(url);
                if (matcher.matches(request)) {
                    //当权限表权限的method为ALL时表示拥有此路径的所有请求方式权利。
                    if (method.equals(request.getMethod()) || "ALL".equals(method)) {
                        return;
                    }
                }
            } else if (ga.getAuthority().equals("ROLE_ANONYMOUS")) {//未登录只允许访问 login 页面
                matcher = new AntPathRequestMatcher("/login");
                if (matcher.matches(request)) {
                    return;
                }
            }
        }
        throw new AccessDeniedException("no right");
    }



    @Override
    public boolean supports(ConfigAttribute attribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }
}

4. 修改MyInvocationSecurityMetadataSourceService 的getAttributes 方法

package com.us.example.service;

import com.us.example.dao.PermissionDao;
import com.us.example.domain.Permission;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.util.*;

/**
 * Created by yangyibo on 17/1/19.
 */
@Service
public class MyInvocationSecurityMetadataSourceService  implements
        FilterInvocationSecurityMetadataSource {

    //此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法,用来判定用户是否有此权限。如果不在权限表中则放行。
    //因为我不想每一次来了请求,都先要匹配一下权限表中的信息是不是包含此url,
    // 我准备直接拦截,不管请求的url 是什么都直接拦截,然后在MyAccessDecisionManager的decide 方法中做拦截还是放行的决策。
    //所以此方法的返回值不能返回 null 此处我就随便返回一下。
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        Collection<ConfigAttribute> co=new ArrayList<>();
        co.add(new SecurityConfig("null"));
        return co;
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }
}

5.关闭csrf

关于 什么是csrf 请看我的这篇博客

spring security CSRF 问题

修改 WebSecurityConfig 的configure(HttpSecurity http) 方法 ,添加 .csrf().disable();

 @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated() //任何请求,登录后可以访问
                .and()
                .formLogin()
                .loginPage("/login")
                .failureUrl("/login?error")
                .permitAll() //登录页面用户任意访问
                .and()
                .logout().permitAll(); //注销行为任意访问
        http.addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class)
                .csrf().disable();


    }

6. 修改HomeController

由于我们是要测试restful 风格的权限,所以我门要有restful 的接口

package com.us.example.controller;

import com.us.example.domain.Msg;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * Created by yangyibo on 17/1/18.
 */
@Controller
public class HomeController {

    @RequestMapping("/")
    public String index(Model model){
        Msg msg =  new Msg("测试标题","测试内容","欢迎来到HOME页面,您拥有 ROLE_HOME 权限");
        model.addAttribute("msg", msg);
        return "home";
    }


    @RequestMapping("/admin")
    @ResponseBody
    public String hello(){
        return "hello admin";
    }

    @RequestMapping("/login")
    public String login(){
        return "login";
    }

    @RequestMapping(value = "/user", method = RequestMethod.GET)
    @ResponseBody
    public String getList(){
        return "hello getList";
    }


    @RequestMapping(value = "/user", method = RequestMethod.POST)
    @ResponseBody
    public String save(){
        return "hello save";
    }


    @RequestMapping(value = "/user", method = RequestMethod.PUT)
    @ResponseBody
    public String update(){
        return "hello update";
    }
}

好了编码部分完成了

三、准备数据

在数据库中添加测试数据,主要是权限表和权限角色中间表。

结果(角色1 可以访问 /user 下的所有接口, 角色2 只可以访问 /user 下的GET请求)
这里写图片描述

权限表:

这里写图片描述

权限角色中间表:(此处角色1拥有 权限 6 ,权限6的方法为 ALL 也就是角色6 可以访问所有路径为/user 的接口)


四、测试

这里写图片描述

启动项目,然后在postman 中测试,
1. 登录admin 后访问 user 的所有权限,都可以正常访问。

这里写图片描述
这里写图片描述

put 方法访问成功 。

  1. 登录abel 后访问 user 的所有权限,只有GET 权限可以访问。
这里写图片描述
这里写图片描述
这里写图片描述

put 方法访问失败。


半夜码字。。。
如果本文对您有帮助请给个好评,谢谢。

本文源码:https://github.com/527515025/springBoot

参考文献:
http://www.cnblogs.com/dongying/p/6106855.html
http://www.cnblogs.com/dongying/p/6128268.html

点这里可以跳转到人工智能网站

发表评论