zoukankan      html  css  js  c++  java
  • springBoot+springSecurity 数据库动态管理用户、角色、权限(二)

    序: 
    本文使用springboot+mybatis+SpringSecurity 实现数据库动态的管理用户、角色、权限管理

    本文细分角色和权限,并将用户、角色、权限和资源均采用数据库存储,并且自定义滤器,代替原有的FilterSecurityInterceptor过滤器, 
    并分别实现AccessDecisionManager、InvocationSecurityMetadataSourceService和UserDetailsService,并在配置文件中进行相应配置。

    spring security的简单原理:

    使用众多的拦截器对url拦截,以此来管理权限。但是这么多拦截器,笔者不可能对其一一来讲,主要讲里面核心流程的两个。

    首先,权限管理离不开登陆验证的,所以登陆验证拦截器AuthenticationProcessingFilter要讲; 
    还有就是对访问的资源管理吧,所以资源管理拦截器AbstractSecurityInterceptor要讲;

    但拦截器里面的实现需要一些组件来实现,所以就有了AuthenticationManager、accessDecisionManager等组件来支撑。

    现在先大概过一遍整个流程,用户登陆,会被AuthenticationProcessingFilter拦截,调用AuthenticationManager的实现,而且AuthenticationManager会调用ProviderManager来获取用户验证信息(不同的Provider调用的服务不同,因为这些信息可以是在数据库上,可以是在LDAP服务器上,可以是xml配置文件上等),如果验证通过后会将用户的权限信息封装一个User放到spring的全局缓存SecurityContextHolder中,以备后面访问资源时使用。 
    访问资源(即授权管理),访问url时,会通过AbstractSecurityInterceptor拦截器拦截,其中会调用FilterInvocationSecurityMetadataSource的方法来获取被拦截url所需的全部权限,在调用授权管理器AccessDecisionManager,这个授权管理器会通过spring的全局缓存SecurityContextHolder获取用户的权限信息,还会获取被拦截的url和被拦截url所需的全部权限,然后根据所配的策略(有:一票决定,一票否定,少数服从多数等),如果权限足够,则返回,权限不够则报错并调用权限不足页面。


    重要

    本文设计和代码是基于 上一篇博客(请点击)

    springboot+mybatis+SpringSecurity 实现用户角色数据库管理

    进行修改。


    本文目录: 
    1:数据库表设计 
    2:权限表的业务 
    3:springSecurity 配置修改 
    4:修改home.html 文件 
    5:修改HomeController.java 文件 
    6:测试检验

    目录结构如下:


    1:数据库表设计

    由于本文增加了权限表所以本文的数据库表为5个分别是: 用户表、角色表、权限表、用户角色中间表、角色权限中间表

    初始化数据

    1.  
      注意:Sys_permission 表的url通配符为两颗星,比如说 /user下的所有url,应该写成 /user/**;
    2.  
      权限的名字可以随意起名
    • insert into SYS_USER (id,username, password) values (1,'admin', 'admin');
      insert into SYS_USER (id,username, password) values (2,'abel', 'abel');
       
      insert into SYS_ROLE(id,name) values(1,'ROLE_ADMIN');
      insert into SYS_ROLE(id,name) values(2,'ROLE_USER');
       
      insert into SYS_ROLE_USER(SYS_USER_ID,ROLES_ID) values(1,1);
      insert into SYS_ROLE_USER(SYS_USER_ID,ROLES_ID) values(2,2);
       
      BEGIN;
      INSERT INTO `Sys_permission` VALUES ('1', 'ROLE_HOME', 'home', '/', null), ('2', 'ROLE_ADMIN', 'ABel', '/admin', null);
      COMMIT;
       
      BEGIN;
      INSERT INTO `Sys_permission_role` VALUES ('1', '1', '1'), ('2', '1', '2'), ('3', '2', '1');
      COMMIT;
      

        

    2:权限表的业务代码

    2.1 java bean

    Permission.java

    1. package com.us.example.domain;
       
      /**
       * Created by yangyibo on 17/1/20.
       */
      public class Permission {
       
          private int id;
          //权限名称
          private String name;
       
          //权限描述
          private String descritpion;
       
          //授权链接
          private String url;
       
          //父节点id
          private int pid;
       
          public int getId() {
              return id;
          }
       
          public void setId(int id) {
              this.id = id;
          }
       
          public String getName() {
              return name;
          }
       
          public void setName(String name) {
              this.name = name;
          }
       
          public String getDescritpion() {
              return descritpion;
          }
       
          public void setDescritpion(String descritpion) {
              this.descritpion = descritpion;
          }
       
          public String getUrl() {
              return url;
          }
       
          public void setUrl(String url) {
              this.url = url;
          }
       
          public int getPid() {
              return pid;
          }
       
          public void setPid(int pid) {
              this.pid = pid;
          }
      }
      

        

    2.2 dao 层

    在 com.us.example.dao 包下新建PermissionDao.java 文件。

    PermissionDao.java

    1. package com.us.example.dao;
      import com.us.example.config.MyBatisRepository;
      import com.us.example.domain.Permission;
      import java.util.List;
       
      /**
       * Created by yangyibo on 17/1/20.
       */
      public interface PermissionDao {
          public List<Permission> findAll();
          public List<Permission> findByAdminUserId(int userId);
      }
      

        

    在src/resource/mapper目录下新建对应的mapper.xml 文件

    PermissionDaoMapper.xml

    1. <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      <mapper namespace="com.us.example.dao.PermissionDao">
      <select id="findAll"  resultType="com.us.example.domain.Permission">
       
         SELECT * from Sys_permission ;
      </select>
       
       <select id="findByAdminUserId" parameterType="int" resultType="com.us.example.domain.Permission">
            select p.*
              from Sys_User u
              LEFT JOIN sys_role_user sru on u.id= sru.Sys_User_id
              LEFT JOIN Sys_Role r on sru.Sys_Role_id=r.id
              LEFT JOIN Sys_permission_role spr on spr.role_id=r.id
              LEFT JOIN Sys_permission p on p.id =spr.permission_id
              where u.id=#{userId}
       </select>
       </mapper>

    3:springSecurity 配置修改

    3.1 修改 WebSecurityConfig.java

    修改com.us.example.config包下的 WebSecurityConfig.java 文件如下:

    1. package com.us.example.config;
       
      import com.us.example.service.CustomUserService;
      import com.us.example.service.MyFilterSecurityInterceptor;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
      import org.springframework.security.config.annotation.web.builders.HttpSecurity;
      import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
      import org.springframework.security.core.userdetails.UserDetailsService;
      import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
       
      /**
       * Created by yangyibo on 17/1/18.
       */
       
       
      @Configuration
      @EnableWebSecurity
      public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
       
          @Autowired
          private MyFilterSecurityInterceptor myFilterSecurityInterceptor;
       
       
       
              @Bean
          UserDetailsService customUserService(){ //注册UserDetailsService 的bean
              return new CustomUserService();
          }
       
          @Override
          protected void configure(AuthenticationManagerBuilder auth) throws Exception {
              auth.userDetailsService(customUserService()); //user Details Service验证
       
          }
       
          @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);
          }
      }

    3.2 修改CustomUserService

    修改CustomUserService.java 内容如下:

    1. package com.us.example.service;
       
      import com.us.example.dao.PermissionDao;
      import com.us.example.dao.UserDao;
      import com.us.example.domain.Permission;
      import com.us.example.domain.SysRole;
      import com.us.example.domain.SysUser;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.security.core.GrantedAuthority;
      import org.springframework.security.core.authority.SimpleGrantedAuthority;
      import org.springframework.security.core.userdetails.User;
      import org.springframework.security.core.userdetails.UserDetails;
      import org.springframework.security.core.userdetails.UserDetailsService;
      import org.springframework.security.core.userdetails.UsernameNotFoundException;
      import org.springframework.stereotype.Service;
       
      import java.util.ArrayList;
      import java.util.List;
       
      /**
       * Created by yangyibo on 17/1/18.
       */
      @Service
      public class CustomUserService implements UserDetailsService { //自定义UserDetailsService 接口
       
          @Autowired
          UserDao userDao;
          @Autowired
          PermissionDao permissionDao;
       
          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 SimpleGrantedAuthority(permission.getName());
                      //1:此处将权限信息添加到 GrantedAuthority 对象中,在后面进行全权限验证时会使用GrantedAuthority 对象。
                      grantedAuthorities.add(grantedAuthority);
                      }
                  }
                  return new User(user.getUsername(), user.getPassword(), grantedAuthorities);
              } else {
                  throw new UsernameNotFoundException("admin: " + username + " do not exist!");
              }
          }
       
      }
      

        

    3.3 新增MyAccessDecisionManager

    在com.us.example.service 包下新增 
    MyAccessDecisionManager.java 文件

    1. 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.stereotype.Service;
       
      import java.util.Collection;
      import java.util.Iterator;
       
      /**
       * Created by yangyibo on 17/1/19.
       */
      @Service
      public class MyAccessDecisionManager implements AccessDecisionManager {
       
        // decide 方法是判定是否拥有权限的决策方法,
        //authentication 是释CustomUserService中循环添加到 GrantedAuthority 对象中的权限信息集合.
        //object 包含客户端发起的请求的requset信息,可转换为 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
        //configAttributes 为MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法返回的结果,此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法,用来判定用户是否有此权限。如果不在权限表中则放行。
          @Override
          public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
       
              if(null== configAttributes || configAttributes.size() <=0) {
                  return;
              }
              ConfigAttribute c;
              String needRole;
              for(Iterator<ConfigAttribute> iter = configAttributes.iterator(); iter.hasNext(); ) {
                  c = iter.next();
                  needRole = c.getAttribute();
                  for(GrantedAuthority ga : authentication.getAuthorities()) {//authentication 为在注释1 中循环添加到 GrantedAuthority 对象中的权限信息集合
                      if(needRole.trim().equals(ga.getAuthority())) {
                          return;
                      }
                  }
              }
              throw new AccessDeniedException("no right");
          }
       
          @Override
          public boolean supports(ConfigAttribute attribute) {
              return true;
          }
       
          @Override
          public boolean supports(Class<?> clazz) {
              return true;
          }
      }

    3.4 新增 MyFilterSecurityInterceptor

    在com.us.example.service 包下新增 
    MyFilterSecurityInterceptor.java 文件

    1. package com.us.example.service;
      import javax.servlet.Filter;
      import javax.servlet.FilterChain;
      import javax.servlet.FilterConfig;
      import javax.servlet.ServletException;
      import javax.servlet.ServletRequest;
      import javax.servlet.ServletResponse;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.security.access.SecurityMetadataSource;
      import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
      import org.springframework.security.access.intercept.InterceptorStatusToken;
      import org.springframework.security.web.FilterInvocation;
      import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
      import org.springframework.stereotype.Service;
       
      import java.io.IOException;
       
      /**
       * Created by yangyibo on 17/1/19.
       */
      @Service
      public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
       
       
          @Autowired
          private FilterInvocationSecurityMetadataSource securityMetadataSource;
       
          @Autowired
          public void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager) {
              super.setAccessDecisionManager(myAccessDecisionManager);
          }
       
       
          @Override
          public void init(FilterConfig filterConfig) throws ServletException {
       
          }
       
          @Override
          public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
       
              FilterInvocation fi = new FilterInvocation(request, response, chain);
              invoke(fi);
          }
       
       
          public void invoke(FilterInvocation fi) throws IOException, ServletException {
      //fi里面有一个被拦截的url
      //里面调用MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法获取fi对应的所有权限
      //再调用MyAccessDecisionManager的decide方法来校验用户的权限是否足够
              InterceptorStatusToken token = super.beforeInvocation(fi);
              try {
      //执行下一个拦截器
                  fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
              } finally {
                  super.afterInvocation(token, null);
              }
          }
       
          @Override
          public void destroy() {
       
          }
       
          @Override
          public Class<?> getSecureObjectClass() {
              return FilterInvocation.class;
          }
       
          @Override
          public SecurityMetadataSource obtainSecurityMetadataSource() {
              return this.securityMetadataSource;
          }
      }

    3.5 新增 MyInvocationSecurityMetadataSourceService

    在com.us.example.service 包下新增MyInvocationSecurityMetadataSourceService.java文件

    1. 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 {
       
          @Autowired
          private PermissionDao permissionDao;
       
          private HashMap<String, Collection<ConfigAttribute>> map =null;
       
          /**
           * 加载权限表中所有权限
           */
          public void loadResourceDefine(){
              map = new HashMap<>();
              Collection<ConfigAttribute> array;
              ConfigAttribute cfg;
              List<Permission> permissions = permissionDao.findAll();
              for(Permission permission : permissions) {
                  array = new ArrayList<>();
                  cfg = new SecurityConfig(permission.getName());
                  //此处只添加了用户的名字,其实还可以添加更多权限的信息,例如请求方法到ConfigAttribute的集合中去。此处添加的信息将会作为MyAccessDecisionManager类的decide的第三个参数。
                  array.add(cfg);
                  //用权限的getUrl() 作为map的key,用ConfigAttribute的集合作为 value,
                  map.put(permission.getUrl(), array);
              }
       
          }
       
      //此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法,用来判定用户是否有此权限。如果不在权限表中则放行。
          @Override
          public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
              if(map ==null) loadResourceDefine();
              //object 中包含用户请求的request 信息
              HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
              AntPathRequestMatcher matcher;
              String resUrl;
              for(Iterator<String> iter = map.keySet().iterator(); iter.hasNext(); ) {
                  resUrl = iter.next();
                  matcher = new AntPathRequestMatcher(resUrl);
                  if(matcher.matches(request)) {
                      return map.get(resUrl);
                  }
              }
              return null;
          }
       
          @Override
          public Collection<ConfigAttribute> getAllConfigAttributes() {
              return null;
          }
       
          @Override
          public boolean supports(Class<?> clazz) {
              return true;
          }
      }

    4:修改home.html 文件

    修改src/resources/templates目录下 的home.html

    1. <!DOCTYPE html>
      <html xmlns:th="http://www.thymeleaf.org" 
            xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
      <head>
      <meta content="text/html;charset=UTF-8"/>
      <title sec:authentication="name"></title>
      <link rel="stylesheet" th:href="@{css/bootstrap.min.css}" />
      <style type="text/css">
      body {
        padding-top: 50px;
      }
      .starter-template {
        padding: 40px 15px;
        text-align: center;
      }
      </style>
      </head>
      <body>
           <nav class="navbar navbar-inverse navbar-fixed-top">
            <div class="container">
              <div class="navbar-header">
                <a class="navbar-brand" href="#">Spring Security演示</a>
              </div>
              <div id="navbar" class="collapse navbar-collapse">
                <ul class="nav navbar-nav">
                 <li><a th:href="@{/}"> 首页 </a></li>
                    <li><a th:href="@{/admin}"> admin </a></li>
                </ul>
              </div><!--/.nav-collapse -->
            </div>
          </nav>
       
       
           <div class="container">
       
            <div class="starter-template">
              <h1 th:text="${msg.title}"></h1>
       
              <p class="bg-primary" th:text="${msg.content}"></p>
       
              <div sec:authorize="hasRole('ROLE_HOME')"> <!-- 用户类型为ROLE_ADMIN 显示 -->
                  <p class="bg-info" th:text="${msg.etraInfo}"></p>
              </div>
                <div sec:authorize="hasRole('ROLE_ADMIN')"> <!-- 用户类型为ROLE_ADMIN 显示 -->
                    <p class="bg-info">恭喜您,您有 ROLE_ADMIN 权限 </p>
                </div>
       
                <form th:action="@{/logout}" method="post">
                  <input type="submit" class="btn btn-primary" value="注销"/>
              </form>
            </div>
       
          </div>
       
       
      </body>
      </html>

    5:修改HomeController.java 文件

    1. 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.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";
          }
      }

    6.测试检验

    启动访问 http://localhost:8080/ 到登录页面

    1.  
      由于数据库的配置 admin 用户拥有 访问 home和admin 页面的权限。
    2.  
      abel 用户只有访问 home 的权限
    • 1
    • 2
    • 3

    使用admin 登录

    这里写图片描述

    点击 admin 按钮 会反回结果 “hello admin“

    使用abel 用户登录 点击 点击 admin 按钮 页面会报403

    源码地址:https://github.com/527515025/springBoot

    参考资料: 
    http://www.tuicool.com/articles/jq6fuur#c-23220 
    http://blog.csdn.net/u012367513/article/details/38866465

  • 相关阅读:
    ios开发遇到的问题
    getopt()——命令行参数分析
    浅谈Samsung Exynos4412处理器
    TTL电平,CMOS电平,232/485电平,OC门,OD门基础知识
    (转载)跟Classic ARM 处理器说拜拜——Atmel SAMA5D3 Xplained开发板评测
    (转载)Quartus II中FPGA的管脚分配保存方法(Quartus II)
    DE2资源集锦
    收到DE2+LCM+ D5M套件,拾回DE2,努力,奋进!
    windows 服务器时间同步失败处理方法
    机械加工仿真软件-----三维弯管机仿真系统
  • 原文地址:https://www.cnblogs.com/telwanggs/p/10802809.html
Copyright © 2011-2022 走看看