springboot-29-security(二)用戶角色權限控制


本博客基於上一個 http://www.cnblogs.com/wenbronk/p/7379865.html

增加了角色的權限表, 可以進行權限校驗

一, 數據准備

1, 數據表建立

/*
Navicat MySQL Data Transfer
Source Server         : 本地
Source Host           : localhost:3306
Source Database       : test
Target Server Type    : MYSQL
Date: 2017-8-14 22:17:33
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for `sys_user`
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
  `id` INT (32) NOT NULL AUTO_INCREMENT COMMENT '主鍵id',
  `username` varchar(32) DEFAULT NULL COMMENT '用戶名',
  `password` varchar(32) DEFAULT NULL COMMENT '密碼',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for `sys_role`
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
  `id` INT (32) NOT NULL AUTO_INCREMENT COMMENT '主鍵id',
  `name` varchar(32) DEFAULT NULL COMMENT '用戶名',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for `sys_permission`
-- ----------------------------
DROP TABLE IF EXISTS `sys_permission`;
CREATE TABLE `sys_permission` (
  `id` INT (32) NOT NULL AUTO_INCREMENT COMMENT '主鍵id',
  `name` varchar(32) DEFAULT NULL COMMENT '用戶名',
  `desc` VARCHAR (32) DEFAULT NULL COMMENT '描述',
  `url` VARCHAR (32) DEFAULT NULL COMMENT 'url',
  `pid` INT (32),
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for `sys_role_user`
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_user`;
CREATE TABLE `sys_role_user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵id',
  `sys_user_id` INT(32) NOT NULL COMMENT 'user_id',
  `sys_role_id` INT(32) NOT NULL COMMENT 'role_id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8;

ALTER TABLE sys_role_user ADD CONSTRAINT sys_FK1 FOREIGN KEY(sys_user_id) REFERENCES sys_user(id);
ALTER TABLE sys_role_user ADD CONSTRAINT role_FK2 FOREIGN KEY(sys_role_id) REFERENCES sys_role(id);

-- ----------------------------
-- Table structure for `sys_permission_role`
-- ----------------------------
DROP TABLE IF EXISTS `sys_permission_role`;
CREATE TABLE `sys_permission_role` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵id',
  `sys_role_id` INT(32) NOT NULL COMMENT 'role_id',
  `sys_permission_id` INT(32) NOT NULL COMMENT 'permission_id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8;
ALTER TABLE sys_permission_role ADD CONSTRAINT sys_FK3 FOREIGN KEY(sys_role_id) REFERENCES sys_role(id);
ALTER TABLE sys_permission_role ADD CONSTRAINT role_FK4 FOREIGN KEY(sys_permission_id) REFERENCES sys_permission(id);

2, 導入數據

insert into SYS_USER (id,username, password) values (1,'vini', '123');
insert into SYS_USER (id,username, password) values (2,'bronk', '123');

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,sys_role_id) values(1,1);
insert into SYS_ROLE_USER(SYS_USER_ID,sys_role_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;

3, mybatis實體, 其余2個和上一篇博客一樣

SysPermission.groovy

package com.wenbronk.security.entity

/**
 * Created by wenbronk on 2017/8/17.
 */
class SysPermission {
    int id
    String name
    String desc
    String url
    int pid
}

 

4, application.yml配置服務啟動導入

和上個一樣

二, security部分

東西比較多, 按流程來, 

1, WebSecurityConfig.groovy 添加

http.addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class)

最終代碼為: 

package com.wenbronk.security.security.config

import com.wenbronk.security.security.interceptor.MyFilterSecurityInterceptor
import com.wenbronk.security.security.service.CustomUserService
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.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor

import javax.inject.Inject
/**
 * Created by wenbronk on 2017/8/15.
 */
@Configuration
@EnableWebSecurity
class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Inject
    CustomUserService customUserService;
    @Autowired
    MyFilterSecurityInterceptor myFilterSecurityInterceptor

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(customUserService);
    }

    @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)
    }
}

2, 請求會被攔截器攔截, MyFilterSecurityInterceptor, 

package com.wenbronk.security.security.interceptor

import com.wenbronk.security.security.config.MyAccessDecisonManager
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.Component

import javax.inject.Inject
import javax.servlet.*

/**
 * Created by wenbronk on 2017/8/17.
 */
@Component
class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {

    @Inject
    FilterInvocationSecurityMetadataSource securityMetadataSource

    @Autowired
    public void setMyAccessDecisionManager(MyAccessDecisonManager myAccessDecisonManager) {
        super.setAccessDecisionManager(myAccessDecisonManager)
    }

    @Override
    void init(FilterConfig filterConfig) throws ServletException {

    }

    /**
     * fi中有一個被攔截的url
     * 里面調用MyInvocationSecurityMetadataSource的getAttributes(Object object)這個方法獲取fi對應的所有權限
     //再調用MyAccessDecisionManager的decide方法來校驗用戶的權限是否足夠
     */
    @Override
    void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        FilterInvocation fi = new FilterInvocation(request, response, chain)
        invoke(fi)
    }

    void invoke(FilterInvocation filterInvocation) {
        InterceptorStatusToken token = super.beforeInvocation(filterInvocation)
        try {
            filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse())
        } finally {
            super.afterInvocation(token, null)
        }
    }

    @Override
    void destroy() {

    }

    @Override
    Class<?> getSecureObjectClass() {
        return FilterInvocation.class
    }

    @Override
    SecurityMetadataSource obtainSecurityMetadataSource() {
        return this.securityMetadataSource
    }
}

3, 攔截器會調用 MyInvocationSecurityMetaDataSource的 getAttribute方法 獲取 filter的權限, 並且滴哦啊用 MyAccessDecisionManager的 decide 方法來校驗是否擁有權限

MyInvocationSecurityMetadataSource.groovy

package com.wenbronk.security.security.service

import com.wenbronk.security.entity.SysPermission
import com.wenbronk.security.mapper.SysPermissionMapper
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.inject.Inject
import javax.servlet.http.HttpServletRequest

/**
 * Created by wenbronk on 2017/8/17.
 */
@Service
class MyInvocationSecurityMetadataSourceService implements FilterInvocationSecurityMetadataSource{

    @Inject
    SysPermissionMapper sysPermissionMapper

    /**
     * 此方法是為了判定用戶請求的url 是否在權限表中,
     * 如果在權限表中,則返回給 decide 方法,用來判定用戶是否有此權限。如果不在權限表中則放行。
     */
    @Override
    Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
        Map<String, Collection<ConfigAttribute>> map = loadResourceDefine()
        HttpServletRequest request = ((FilterInvocation) o).getHttpRequest()
        map.each {entry ->
            AntPathRequestMatcher matcher = new AntPathRequestMatcher(entry.getKey())
            if (matcher.matches(request)) {
                // 匹配, 返回給decide方法
                return entry.getValue()
            }
        }
        return null
    }

    /**
     * 加載權限表中所有的權限
     */
    public Map<String, Collection<ConfigAttribute>> loadResourceDefine() {
        Map<String, Collection<ConfigAttribute>> map = new HashMap<>()
        List<SysPermission> permissions = sysPermissionMapper.findAll()
        permissions.each {permission ->
            List<ConfigAttribute> array = new ArrayList<>()
            // 此處只添加了用戶的名字,其實還可以添加更多權限的信息,例如請求方法到ConfigAttribute的集合中去。
            // 此處添加的信息將會作為MyAccessDecisionManager類的decide的第三個參數。
            ConfigAttribute cfg = new SecurityConfig(permission.getName())
            array.add(cfg)
            // 用權限的url作為key, 權限的名稱集合為value
            map.put(permission.getUrl(), array);
        }

        return null
    }

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

    @Override
    boolean supports(Class<?> aClass) {
        return true
    }
}

4, MyInvocationSecurityMetadataSource 查詢數據庫中url和所需權限的關系, 返回給 MyAccessDecisionManger

MyAccessDecisionManger.groovy

package com.wenbronk.security.security.config

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.stereotype.Service

/**
 * Created by wenbronk on 2017/8/17.
 */
@Service
class MyAccessDecisonManager implements AccessDecisionManager {

    /**
     * 判斷是否擁有權限的決策方法
     * authentication 是CustomUserService中循環添加到 GrantedAuthority 對象中的權限信息集合.
     * object 包含客戶端發起的請求的requset信息,可轉換為 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
     * configAttributes 為MyInvocationSecurityMetadataSource的getAttributes(Object object)這個方法返回的結果,
     */
    @Override
    void decide(Authentication authentication, Object o, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        if (null == configAttributes || configAttributes.size() <=0 )
            return
        configAttributes.each {configAttribute ->
            String needRole = configAttribute.getAttribute()
            authentication.getAuthorities().each {authority ->
                if (needRole.trim().equals(authority.getAuthority()))
                    return
            }
        }
        throw new java.nio.file.AccessDeniedException('no right')
    }

    @Override
    boolean supports(ConfigAttribute configAttribute) {
        return true
    }

    @Override
    boolean supports(Class<?> aClass) {
        return true
    }
}

decide進行是否有權限的決策, 正常則返回, 沒有就拋出異常

5, 最終在 customuserService進行權限和用戶的裝配, 准備返回給前端

package com.wenbronk.security.security.service

import com.wenbronk.security.entity.SysPermission
import com.wenbronk.security.entity.SysUser
import com.wenbronk.security.mapper.SysPermissionMapper
import com.wenbronk.security.mapper.SysUserMapper
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 javax.inject.Inject
/**
 * Created by wenbronk on 2017/8/15.
 */
@Service
class CustomUserService implements UserDetailsService {

    @Inject
    SysUserMapper sysUserMapper
    @Inject
    SysPermissionMapper sysPermissionMapper

    @Override
    UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        def sysUser = sysUserMapper.findByUserName(s) as SysUser
        if (sysUser != null) {
            List<SysPermission> permissions = sysPermissionMapper.findByAdminUserId(sysUser.getId())
            List<GrantedAuthority> grantedAuthorities = new ArrayList<>()
            permissions.each {permission ->
                if (permission != null && permission.getName() != null) {
                    GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(permission.getName())
                    // 此處將權限信息添加到 GrantedAuthority 對象中,在后面進行全權限驗證時會使用GrantedAuthority 對象。
                    grantedAuthorities.add(grantedAuthority)
                }
            }
            return new User(sysUser.getUsername(), sysUser.getPassword(), grantedAuthorities)
        }else {
            throw new UsernameNotFoundException('admin: ' + s + ' do not exits')
        }
    }
}

三: 頁面部分

1, 頁面轉向config, 和上一篇的一樣

@Configuration
class WebMvcConfig extends WebMvcConfigurerAdapter{

    @Override
    void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("login")
//        registry.addViewController("/home").setViewName("home")
    }
}

2, 更改home.html

<!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_USER 顯示 -->
             <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>

 

四, 密碼進行加密

注意, 如果使用, 存儲進數據庫的密碼也必須是同一個算法計算的密文

md5, 工具類

package com.wenbronk.security.tools

import java.security.MessageDigest

/**
 * Created by wenbronk on 2017/8/17.
 */
class MD5Utils {
    private static final String SALT = "tamboo";

    public static String encode(String password) {
        password = password + SALT;
        MessageDigest md5 = null;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        char[] charArray = password.toCharArray();
        byte[] byteArray = new byte[charArray.length];

        for (int i = 0; i < charArray.length; i++)
            byteArray[i] = (byte) charArray[i];
        byte[] md5Bytes = md5.digest(byteArray);
        StringBuffer hexValue = new StringBuffer();
        for (int i = 0; i < md5Bytes.length; i++) {
            int val = ((int) md5Bytes[i]) & 0xff;
            if (val < 16) {
                hexValue.append("0");
            }

            hexValue.append(Integer.toHexString(val));
        }
        return hexValue.toString();
    }

    public static void main(String[] args) {
        System.out.println(MD5Utils.encode("abel"));
    }
}

2, 修改 webSecurityConfig的加密方法

@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserService()).passwordEncoder(
new PasswordEncoder(){ @Override public String encode(CharSequence rawPassword) { return MD5Util.encode((String)rawPassword); } @Override public boolean matches(CharSequence rawPassword, String encodedPassword) { return encodedPassword.equals(MD5Util.encode((String)rawPassword)); }}); //user Details Service驗證 }

 BCrypt的強hash算法

 BCrypt強哈希方法 每次加密的結果都不一樣。但是存貯其中一次加密結果 也能夠驗證成功

1, 修改WebSecurityConfig

    @Autowired
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(customUserService).passwordEncoder(new BCryptPasswordEncoder());
    }

2, 進行加密

  public SysUser create(User u user){
        //進行加密
        BCryptPasswordEncoder encoder =new BCryptPasswordEncoder();
        sysUser.setPassword(encoder.encode(user.getRawPassword().trim()));
        userDao.create(user);
    return sysUser;

 

原博客地址: 

http://blog.csdn.net/u012373815/article/details/54633046

http://blog.csdn.net/u012373815/article/details/60465776


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM