基於角色的權限管理
什麼是角色?
- 代表一系列行爲或責任的實體
- 限定能做什麼、不能做什麼
- 用戶賬號往往與角色相關聯
我們在談到程序權限管理的話,能不能想到角色這一個概念,角色是代表了一系列行爲或者責任的實體,用於限制在系統中能做什麼,不能做什麼,一般來說,一個用戶的賬號在系統中能做什麼,往往取決於這個用戶是什麼角色,比如一個用戶他是一個項目管理員,他就能做這個項目管理員能做的事情,比如說,他可以查看項目中的應用,管理項目組中的成員,可以導出項目報表等等,所以說用戶的話關聯了角色之後,他就有了一相關角色的一些操作權限 ,所以,角色是一種行爲的概念,表示用戶能在系統中進行的一些操作。
RBAC
- 基於角色的訪問控制(Role-Based Access Control)
- 隱式訪問控制
if(user.hasRole("Project Manager")){
//顯示按鈕
}else{
//不顯示按鈕
}
隱式訪問控制與角色是密切關聯的,假設角色名稱改變的話,代碼可能也要做相應的改變
- 顯示訪問控制
if(user.isPermitted("projectReport:view:12345")){
//顯示按鈕
}else{
//不顯示按鈕
}
與角色沒有直接關聯,而是判斷是否擁有某種權限,這個權限與角色關聯,用戶再和角色關聯。最終實現用戶與權限的關聯,這種方式相比隱式訪問控制,就比較靈活了。
權限管理解決方案
- Apache Shiro
Apache Shiro 相對於SpringSecurity來說,比較輕量級,使用起來比較簡單一些 - Spring Security
SpringSecurity在使用上來說比ApacheShiro功能更多一點、更強大一點。在Spring應用中,它是在兼容性以及在支持方面都比Shiro要好一點。Security整個社區也比較完善,發展上前景也比較好。
SpringSecurity 簡介
在JavaEE企業級應用中提供全面的安全服務,特別是在Spring應用中。SpringSecurity經常會做一些集成。SpringSecurity與Apache Shiro其實有很多的相似點。
核心領域概念
- 認證(authentication):“認證” 是建立主體(principal)的過程。“主體”通常是指可以在應用程序中執行操作的用戶、設備或其他系統
- 授權(authorization):或稱爲“訪問控制(accell-control)”,"授權"是指決定是否允許主體在應用程序中執行一些相關的操作。
身份驗證技術
- HTTP BASIC
- HTTP Digest
- HTTP X.509
- LDAP
- 基於表單的認證
- OpenID
- 單點登錄
- Remember-Me
- 匿名身份驗證
- Run-as
- JAAS
- JavaEE 容器認證
模塊
SpringSecurity是模塊化的
- Core - spring-security-core.jar:包含認證,授權接口等等
- Remoting- spring-security-remoting.jar
- Web - spring-security-web.jar
- Config - spring-security-config.jar : 配置
- LDAP - spring-security-ldap.jar
- ACL - spring-security-acl.jar
- CAS - spring-security-cas.jar : 單點登錄
- OpenID - spring-security-openid.jar
- Test - spring-security-test.jar
SpringSecurity與SpringBoot集成
依賴
<!-- springboot2.2.2 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
</parent>
<!-- springboot已經對thymeleaf做了集成,版本不用寫 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- springboot已經對security做了集成,版本不用寫 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- 官方並沒有對thymeleaf-extras-springsecurity做集成,所以要將此依賴引入進來 -->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
SpringSecurity實戰
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.giteasy</groupId>
<artifactId>security-in-action</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>security-in-action</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
創建安全配置類SecurityConfig.java
package cn.giteasy.bootstrap.config;
import org.springframework.beans.factory.annotation.Autowired;
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.crypto.bcrypt.BCryptPasswordEncoder;
/**
* 安全配置類
* Created by Axin in 2019/12/23 21:46
*/
@EnableWebSecurity //啓用Security
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 自定義配置
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/css/**","/js/**","/fonts/**","/index").permitAll()//都可以訪問
.antMatchers("/users/**").hasRole("ADMIN")//需要ADMIN的角色才能訪問
.and()
.formLogin()//基於form表單認證
.loginPage("/login") //自定義登錄頁面
.failureUrl("/login-error");//登錄失敗頁面
}
/**
* 認證信息管理
* @param auth
* @throws Exception
*/
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception{
auth.inMemoryAuthentication()//爲了演示方便我們將認證信息存儲在內存中
.passwordEncoder(new BCryptPasswordEncoder())
.withUser("axin") //用於演示的用戶名
.password(new BCryptPasswordEncoder().encode("123456"))//密碼
.roles("ADMIN");//角色名稱
}
}
相應的Controller:MainController.java
package cn.giteasy.bootstrap.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
/**
* 主頁控制器
* Created by Axin in 2019/12/23 21:58
*/
@Controller
public class MainController {
@GetMapping("/")
public String root(){
return "redirect:/index";
}
@GetMapping("/index")
public String index(){
return "index";
}
@GetMapping("/login")
public String login(){
return "login";
}
@GetMapping("/login-error")
public String loginError(Model model){
model.addAttribute("loginError",true);
model.addAttribute("errorMsg","登錄失敗,用戶名或密碼錯誤!");
//登錄失敗後,還是會返回登錄頁面,但是會攜帶錯誤信息
return "login";
}
}
前端頁面編寫:
這裏只提供了關鍵代碼,如果需要查看祥細代碼,文章結尾有github鏈接
公共header.html
添加命令空間:
<html data-th-fragment="header"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
......
......
......
......
<!--登錄判斷:如果已登錄,顯示用戶名和退出按鈕-->
<div sec:authorize="isAuthenticated()" class="row">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<span class="nav-link" sec:authentication="name"></span>
</li>
</ul>
<form action="/logout" th:action="@{/logout}" method="post">
<input class="btn btn-outline-success" type="submit" value="退出">
</form>
</div>
<!--登錄判斷:如果未登錄,顯示登錄按鈕-->
<div sec:authorize="isAnonymous()">
<a href="/login" th:href="@{~/login}" class="btn btn-outline-success my-2 my-sm-0" type="submit">登錄</a>
</div>
...
...
...
</html>
index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head th:replace="~{fragments/header :: header}">
<!--th:replace="~{fragments/header :: header}":會將header.html頁面內容替換到這個dom節點下-->
</head>
<body>
<div class="container blog-content-container">
<!--登錄判斷:如果已登錄,顯示用戶名和用戶角色-->
<div sec:authorize="isAuthenticated()">
<p>已登錄</p>
<p>用戶名:<span sec:authentication="name"></span></p>
<p>角 色:<span sec:authentication="principal.authorities"></span></p>
</div>
<!--登錄判斷:如果未登錄,提示未登錄信息-->
<div sec:authorize="isAnonymous()">
<p>未登錄</p>
</div>
</div>
<div th:replace="~{fragments/footer :: footer}">...</div>
</body>
</html>
login.html
...
...
...
<!--
/login: 我們並沒有在Controller中定義/login接口,
而在SecurityConfig.java文件中定義了/login接口,
security會自動攔截登錄請求進行匹配賬號和密碼 進行認證
-->
<form action="/login" method="POST" th:action="@{/login}">
<h3>請登錄</h3>
<div class="from-group col-md-5">
<label for="username" class="col-form-label">賬號</label>
<input type="text" class="form-control" id="username" name="username" maxlength="50" placeholder="請輸入賬號">
</div>
<div class="from-group col-md-5">
<label for="password" class="col-form-label">密碼</label>
<input type="text" class="form-control" id="password" name="password" maxlength="50" placeholder="請輸入密碼">
</div>
<div class="from-group col-md-5">
<button type="submit" class="btn btn-primary">登錄</button>
</div>
<!--登錄失敗,重定向到此而面,顯示登錄失敗信息-->
<div class="col-md-5" th:if="${loginError}">
<p class="blog-label-error" th:text="${errorMsg}"></p>
</div>
</form>
...
...
...
項目目錄結構,只標記關鍵文件
頁面效果
github:https://github.com/gitAxin/security-in-action.git