<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<groupId>sec_test</groupId>
<artifactId>sec</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-boot.version>2.0.1.RELEASE</spring-boot.version>
</properties>
<dependencies>
<!-- spring-boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.19</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>${project.name}</finalName>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
server:
port: 8090
application:
name: sec
spring:
thymeleaf:
mode: HTML5
encoding: UTF-8
content-type: text/html
cache: false #開發時關閉緩存,不然沒法看到實時頁面!
prefix: classpath:/public/ #配置頁面文件路徑
suffix: .html #配置頁面默認后綴
datasource:
url: jdbc:mysql://127.0.0.1:3306/sec?useUnicode=true&characterEncoding=UTF-8
username: root
password: ******
package com.veiking;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 項目啟動入口
* @author Veiking
*/
@SpringBootApplication
public class SecApplication {
public static void main(String[] args) {
SpringApplication.run(SecApplication.class, args);
}
}
-- ----------------------------
-- Table structure for `s_user`
-- ----------------------------
DROP TABLE IF EXISTS `s_user`;
CREATE TABLE `s_user` (
`id` int(11) NOT NULL,
`name` varchar(32) DEFAULT NULL,
`password` varchar(32) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for `s_user_role`
-- ----------------------------
DROP TABLE IF EXISTS `s_user_role`;
CREATE TABLE `s_user_role` (
`fk_user_id` int(11) DEFAULT NULL,
`fk_role_id` int(11) DEFAULT NULL,
KEY `union_key` (`fk_user_id`,`fk_role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for `s_role`
-- ----------------------------
DROP TABLE IF EXISTS `s_role`;
CREATE TABLE `s_role` (
`id` int(11) NOT NULL,
`role` varchar(32) DEFAULT NULL,
`describe` varchar(32) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for `s_role_permission`
-- ----------------------------
DROP TABLE IF EXISTS `s_role_permission`;
CREATE TABLE `s_role_permission` (
`fk_role_id` int(11) DEFAULT NULL,
`fk_permission_id` int(11) DEFAULT NULL,
KEY `union_key` (`fk_role_id`,`fk_permission_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for `s_permission`
-- ----------------------------
DROP TABLE IF EXISTS `s_permission`;
CREATE TABLE `s_permission` (
`id` int(11) NOT NULL,
`permission` varchar(32) DEFAULT NULL,
`url` varchar(32) DEFAULT NULL,
`describe` varchar(32) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of s_user
-- ----------------------------
INSERT INTO `s_user` VALUES ('1', 'admin', 'admin');
INSERT INTO `s_user` VALUES ('2', 'veiking', 'veiking');
INSERT INTO `s_user` VALUES ('3', 'xiaoming', 'xiaoming');
-- ----------------------------
-- Records of s_user_role
-- ----------------------------
INSERT INTO `s_user_role` VALUES ('1', '1');
INSERT INTO `s_user_role` VALUES ('2', '2');
-- ----------------------------
-- Records of s_role
-- ----------------------------
INSERT INTO `s_role` VALUES ('1', 'R_ADMIN', '大總管,所有權限');
INSERT INTO `s_role` VALUES ('2', 'R_HELLO', '說hello相關的權限');
-- ----------------------------
-- Records of s_role_permission
-- ----------------------------
INSERT INTO `s_role_permission` VALUES ('1', '1');
INSERT INTO `s_role_permission` VALUES ('1', '2');
INSERT INTO `s_role_permission` VALUES ('1', '3');
INSERT INTO `s_role_permission` VALUES ('2', '1');
INSERT INTO `s_role_permission` VALUES ('2', '3');
-- ----------------------------
-- Records of s_permission
-- ----------------------------
INSERT INTO `s_permission` VALUES ('1', 'P_INDEX', '/index', 'index頁面資源');
INSERT INTO `s_permission` VALUES ('2', 'P_ADMIN', '/admin', 'admin頁面資源');
INSERT INTO `s_permission` VALUES ('3', 'P_HELLO', '/hello', 'hello頁面資源');
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta content="text/html;charset=UTF-8"/>
<title>登錄</title>
<link rel="stylesheet" th:href="@{css/bootstrap.min.css}"/>
<style type="text/css">
body { padding: 20px; }
.starter-template { width:350px; padding: 0 40px; text-align: center; }
</style>
</head>
<body>
<a th:href="@{/index}"> INDEX</a>
<a th:href="@{/admin}"> | ADMIN</a>
<a th:href="@{/hello}"> | HELLO</a>
<br/>
<hr/>
<div class="starter-template">
<p th:if="${param.logout}" class="bg-warning">已成功注銷
有錯誤,請重試
<h2>使用用戶名密碼登錄</h2>
<form name="form" th:action="@{/login}" action="/login" method="POST">
<div class="form-group">
<label for="username">賬號</label>
<input type="text" class="form-control" name="username" value="" placeholder="賬號" />
</div>
<div class="form-group">
<label for="password">密碼</label>
<input type="password" class="form-control" name="password" placeholder="密碼" />
</div>
<div class="form-group">
<input type="submit" id="login" value="登錄" class="btn btn-primary" />
</div>
</form>
</div>
</body>
</html>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
<title>主頁</title>
<link rel="stylesheet" th:href="@{css/bootstrap.min.css}"/>
<style type="text/css">
body { padding: 40px; }
</style>
</head>
<body>
<h1>INDEX</h1>
<br/>你好:<a sec:authentication="name"></a>
<a th:href="@{/index}"> INDEX</a>
<a th:href="@{/admin}"> | ADMIN</a>
<a th:href="@{/hello}"> | HELLO</a>
<br/><hr/>
<form th:action="@{/logout}" method="post">
<input type="submit" class="btn btn-primary" value="注銷"/>
</form>
</body>
</html>
package com.veiking.sec.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 用戶名密碼信息
* @author Veiking
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SUser {
private int id;
private String name;
private String password;
public SUser(SUser sUser) {
this.id = sUser.getId();
this.name = sUser.getName();
this.password = sUser.getPassword();
}
}
package com.veiking.sec.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 角色信息
* @author Veiking
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SRole {
private int id;
private String role;
private String describe;
}
package com.veiking.sec.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 訪問資源信息
* @author Veiking
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SPermission {
private int id;
private String permission;
private String url;
private String describe;
}
package com.veiking.sec.dao;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import com.veiking.sec.bean.SUser;
/**
* 用戶信息查詢
* @author Veiking
*/
@Mapper
public interface SUserDao {
/**
* 根據用戶名獲取用戶
*
* @param name
* @return
*/
@Select(value = " SELECT su.* FROM s_user su WHERE su.name = #{name} ")
public SUser findSUserByName(String name);
}
package com.veiking.sec.dao;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import com.veiking.sec.bean.SRole;
/**
* 角色信息查詢
* @author Veiking
*/
@Mapper
public interface SRoleDao {
/**
* 根據用戶ID獲取角色列表
* @param sUserId
* @return
*/
@Select(value=" SELECT sr.* FROM s_role sr " +
" LEFT JOIN s_user_role sur ON sr.id = sur.fk_role_id " +
" LEFT JOIN s_user su ON sur.fk_user_id = su.id " +
" WHERE su.id = #{sUserId} ")
public List<SRole> findSRoleListBySUserId(int sUserId);
/**
* 根據資源路徑獲取角色列表
* @param sPermissionUrl
* @return
*/
@Select(value=" SELECT sr.* FROM s_role sr " +
" LEFT JOIN s_role_permission srp ON sr.id = srp.fk_role_id " +
" LEFT JOIN s_permission sp ON srp.fk_permission_id = sp.id " +
" WHERE sp.url = #{sPermissionUrl} ")
public List<SRole> findSRoleListBySPermissionUrl(String sPermissionUrl);
}
package com.veiking.sec.dao;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import com.veiking.sec.bean.SPermission;
/**
* 資源權限信息查詢
* @author Veiking
*/
@Mapper
public interface SPermissionDao {
/**
* 根據用戶ID獲取資源權限列表
* @param sUserId
* @return
*/
@Select(value=" SELECT * FROM s_permission sp " +
" LEFT JOIN s_role_permission srp ON sp.id = srp.fk_permission_id " +
" LEFT JOIN s_role sr ON srp.fk_role_id = sr.id " +
" LEFT JOIN s_user_role sur ON sr.id = sur.fk_role_id " +
" LEFT JOIN s_user su ON sur.fk_user_id = su.id " +
" WHERE su.id = #{sUserId} ")
public List<SPermission> findSPermissionListBySUserId(int sUserId);
/**
* 根據資源路徑獲取資源權限列表
* @param sPermissionUrl
* @return
*/
@Select(value=" SELECT * FROM s_permission sp WHERE sp.url = #{sUserId} ")
public List<SPermission> findSPermissionListBySPermissionUrl(String sPermissionUrl);
}
package com.veiking.sec.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.veiking.sec.bean.SPermission;
import com.veiking.sec.bean.SRole;
import com.veiking.sec.bean.SUser;
import com.veiking.sec.dao.SPermissionDao;
import com.veiking.sec.dao.SRoleDao;
import com.veiking.sec.dao.SUserDao;
/**
* Security 數據服務
*
* @author Veiking
*/
@Service
public class SecurityDataService {
@Autowired
private SUserDao sUserDao;
@Autowired
private SRoleDao sRoleDao;
@Autowired
private SPermissionDao sPermissionDao;
public SUser findSUserByName(String name) {
return sUserDao.findSUserByName(name);
}
public List<SRole> findSRoleListBySUserId(int sUserId) {
return sRoleDao.findSRoleListBySUserId(sUserId);
}
public List<SRole> findSRoleListBySPermissionUrl(String sPermissionUrl) {
return sRoleDao.findSRoleListBySPermissionUrl(sPermissionUrl);
}
public List<SPermission> findSPermissionListBySUserId(int sUserId) {
return sPermissionDao.findSPermissionListBySUserId(sUserId);
}
public List<SPermission> findSPermissionListBySPermissionUrl(String sPermissionUrl) {
return sPermissionDao.findSPermissionListBySPermissionUrl(sPermissionUrl);
}
}
package com.veiking.sec.authentication;
import java.util.Collection;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import com.google.gson.Gson;
import com.veiking.sec.bean.SPermission;
import com.veiking.sec.bean.SRole;
import com.veiking.sec.bean.SUser;
/**
* 用戶信息的封裝,包含用戶名稱密碼及用戶狀態、權限等信息
* @author Veiking
*/
public class VUserDetails extends SUser implements UserDetails{
private static final long serialVersionUID = 1L;
Gson gson = new Gson();
Logger logger = LoggerFactory.getLogger(this.getClass());
//用戶角色列表
private List<SRole> sRoleList = null;
//用戶資源權限列表
private List<SPermission> sPermissionList = null;
/**
* 注意后邊的這兩個參數:sRoleList、sPermissionList
* @param sUser
* @param sRoleList
* @param sPermissionList
*/
public VUserDetails(SUser sUser, List<SRole> sRoleList, List<SPermission> sPermissionList) {
super(sUser);
this.sRoleList = sRoleList;
this.sPermissionList = sPermissionList;
}
/**
* 獲取用戶權限列表方法
* 可以理解成,返回了一個List<String>,之后所謂的權限控制、鑒權,其實就是跟這個list里的String進行對比
* 這里處理了角色和資源權限兩個列表,可以這么理解,
* 角色是權限的抽象集合,是為了更方便的控制和分配權限,而真正顆粒化細節方面,還是需要資源權限自己來做
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
StringBuilder authoritiesBuilder = new StringBuilder("");
List<SRole> tempRoleList = this.getsRoleList();
if (null != tempRoleList) {
for (SRole role : tempRoleList) {
authoritiesBuilder.append(",").append(role.getRole());
}
}
List<SPermission> tempPermissionList = this.getsPermissionList();
if (null != tempPermissionList) {
for (SPermission permission : tempPermissionList) {
authoritiesBuilder.append(",").append(permission.getPermission());
}
}
String authoritiesStr = "";
if(authoritiesBuilder.length()>0) {
authoritiesStr = authoritiesBuilder.deleteCharAt(0).toString();
}
logger.info("VUserDetails getAuthorities [authoritiesStr={} ", authoritiesStr);
return AuthorityUtils.commaSeparatedStringToAuthorityList(authoritiesStr);
}
@Override
public String getPassword() {
return super.getPassword();
}
@Override
public String getUsername() {
return super.getName();
}
/**
* 判斷賬號是否已經過期,默認沒有過期
*/
@Override
public boolean isAccountNonExpired() {
// TODO Auto-generated method stub
return true;
}
/**
* 判斷賬號是否被鎖定,默認沒有鎖定
*/
@Override
public boolean isAccountNonLocked() {
return true;
}
/**
* 判斷信用憑證是否過期,默認沒有過期
*/
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
* 判斷賬號是否可用,默認可用
*/
@Override
public boolean isEnabled() {
return true;
}
public List<SRole> getsRoleList() {
return sRoleList;
}
public void setsRoleList(List<SRole> sRoleList) {
this.sRoleList = sRoleList;
}
public List<SPermission> getsPermissionList() {
return sPermissionList;
}
public void setsPermissionList(List<SPermission> sPermissionList) {
this.sPermissionList = sPermissionList;
}
}
package com.veiking.sec.authentication;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
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 com.veiking.sec.bean.SPermission;
import com.veiking.sec.bean.SRole;
import com.veiking.sec.bean.SUser;
import com.veiking.sec.service.SecurityDataService;
/**
* 提供用戶信息封裝服務
* @author Veiking
*/
@Service
public class VUserDetailsService implements UserDetailsService {
@Autowired
SecurityDataService securityDataService;
/**
* 根據用戶輸入的用戶名返回數據源中用戶信息的封裝,返回一個UserDetails
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SUser sUser = securityDataService.findSUserByName(username);
//用戶角色列表
List<SRole> sRoleList = securityDataService.findSRoleListBySUserId(sUser.getId());
//用戶資源權限列表
List<SPermission> sPermissionList = securityDataService.findSPermissionListBySUserId(sUser.getId());
return new VUserDetails(sUser, sRoleList, sPermissionList);
}
}
package com.veiking.sec.authentication;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
import com.google.gson.Gson;
/**
* 認證提供者,校驗用戶,登錄名密碼,以及向系統提供一個用戶信息的綜合封裝
* @author Veiking
*/
@Component
public class VAuthenticationProvider implements AuthenticationProvider {
Gson gson = new Gson();
Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
VUserDetailsService vUserDetailsService;
/**
* 首先,在用戶登錄的時候,系統將用戶輸入的的用戶名和密碼封裝成一個Authentication對象
* 然后,根據用戶名去數據源中查找用戶的數據,這個數據是封裝成的VUserDetails對象
* 接著,將兩個對象進行信息比對,如果密碼正確,通過校驗認證
* 最后,將用戶信息(含身份信息、細節信息、密碼、權限等)封裝成一個對象,此處參考UsernamePasswordAuthenticationToken
* 最最后,會將這個對象交給系統SecurityContextHolder中(功能類似Session),以便后期隨時取用
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
logger.info("VAuthenticationProvider authenticate login user [username={}, password={}]", username, password);
VUserDetails vUserDetails = (VUserDetails)vUserDetailsService.loadUserByUsername(username);
logger.info("VAuthenticationProvider authenticate vUserDetails [vUserDetails={}]", gson.toJson(vUserDetails));
if(vUserDetails == null){
throw new BadCredentialsException("用戶沒有找到");
}
if (!password.equals(vUserDetails.getPassword())) {
logger.info("VAuthenticationProvider authenticate BadCredentialsException [inputPassword={}, DBPassword={}]", password, vUserDetails.getPassword());
throw new BadCredentialsException("密碼錯誤");
}
//認證校驗通過后,封裝UsernamePasswordAuthenticationToken返回
return new UsernamePasswordAuthenticationToken(vUserDetails, password, vUserDetails.getAuthorities());
}
@Override
public boolean supports(Class<?> authentication) {
return true;
}
}
package com.veiking.sec;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 訪問路徑配置類
* 可以理解成做簡單訪問過濾的,轉發到相應的視圖頁面
* @author Veiking
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
registry.addViewController("/").setViewName("index");
registry.addViewController("/index").setViewName("index");
}
}
package com.veiking.sec.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* 請求頁面分發,注意和WebMvcConfig的對比,功能類似
* @author Veiking
*/
@Controller
public class PageController {
@RequestMapping("/admin")
public String admin(Model model, String tt) {
return "admin";
}
@RequestMapping("/hello")
public String hello(Model model, String tt) {
return "hello";
}
}
package com.veiking.sec;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* Security 主配置文件
* @author Veiking
*/
@Configuration
@EnableWebSecurity //開啟Spring Security的功能
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 定義不需要過濾的靜態資源(等價于HttpSecurity的permitAll)
*/
@Override
public void configure(WebSecurity webSecurity) throws Exception {
webSecurity.ignoring().antMatchers("/css/**");
}
/**
* 安全策略配置
*/
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.authorizeRequests()
// 對于網站部分資源需要指定鑒權
//.antMatchers("/admin/**").hasRole("ADMIN")
// 除上面外的所有請求全部需要鑒權認證
.anyRequest().authenticated().and()
// 定義當需要用戶登錄時候,轉到的登錄頁面
.formLogin().loginPage("/login").defaultSuccessUrl("/index").permitAll().and()
// 定義登出操作
.logout().logoutSuccessUrl("/login?logout").permitAll().and()
.csrf().disable()
;
// 禁用緩存
httpSecurity.headers().cacheControl();
}
}
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
<meta content="text/html;charset=UTF-8"/>
<title>HELLO</title>
<link rel="stylesheet" th:href="@{css/bootstrap.min.css}"/>
<style type="text/css">
body { padding: 40px; }
</style>
</head>
<body>
<h1>HELLO</h1>
<br/>你好:<a sec:authentication="name"></a>
<a sec:authorize="hasAuthority('P_INDEX')" th:href="@{/index}"> INDEX</a>
<a sec:authorize="hasAuthority('P_ADMIN')" th:href="@{/admin}"> | ADMIN</a>
<a sec:authorize="hasAuthority('P_HELLO')" th:href="@{/hello}"> | HELLO</a>
<br/><hr/>
<form th:action="@{/logout}" method="post" sec:authorize="hasAuthority('R_ADMIN')">
<input type="submit" class="btn btn-primary" value="注銷"/>
</form>
</body>
</html>
package com.veiking.sec.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* 請求頁面分發,注意和WebMvcConfig的對比,功能類似
* @author Veiking
*/
@Controller
public class PageController {
@RequestMapping("/admin")
@PreAuthorize("hasAuthority('R_ADMIN')")
public String admin(Model model, String tt) {
return "admin";
}
@RequestMapping("/hello")
public String hello(Model model, String tt) {
return "hello";
}
}
package com.veiking.sec;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* Security 主配置文件
* @author Veiking
*/
@Configuration
@EnableWebSecurity //開啟Spring Security的功能
@EnableGlobalMethodSecurity(prePostEnabled=true)//開啟注解控制權限
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 定義不需要過濾的靜態資源(等價于HttpSecurity的permitAll)
*/
@Override
public void configure(WebSecurity webSecurity) throws Exception {
webSecurity.ignoring().antMatchers("/css/**");
}
/**
* 安全策略配置
*/
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.authorizeRequests()
// 對于網站部分資源需要指定鑒權
//.antMatchers("/admin/**").hasRole("ADMIN")
// 除上面外的所有請求全部需要鑒權認證
.anyRequest().authenticated().and()
// 定義當需要用戶登錄時候,轉到的登錄頁面
.formLogin().loginPage("/login").defaultSuccessUrl("/index").permitAll().and()
// 定義登出操作
.logout().logoutSuccessUrl("/login?logout").permitAll().and()
.csrf().disable()
;
// 禁用緩存
httpSecurity.headers().cacheControl();
}
/**
* 開啟注解控制權限
* 見Controller 中 @PreAuthorize("hasAuthority('XXX')")
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
package com.veiking.sec.authorization;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.stereotype.Component;
import com.google.gson.Gson;
import com.veiking.sec.bean.SPermission;
import com.veiking.sec.bean.SRole;
import com.veiking.sec.service.SecurityDataService;
/**
* 權限資源管理器
* 根據用戶請求的地址,獲取訪問該地址需要的所需權限
* @author Veiking
*/
@Component
public class VFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
Gson gson = new Gson();
Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
SecurityDataService securityDataService;
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
//獲取請求起源路徑
String requestUrl = ((FilterInvocation) object).getRequestUrl();
logger.info("VFilterInvocationSecurityMetadataSource getAttributes [requestUrl={}]", requestUrl);
//登錄頁面就不需要權限
if ("/login".equals(requestUrl)) {
return null;
}
//用來存儲訪問路徑需要的角色或權限信息
List<String> tempPermissionList = new ArrayList<String>();
//獲取角色列表
List<SRole> sRoleList = securityDataService.findSRoleListBySPermissionUrl(requestUrl);
logger.info("VFilterInvocationSecurityMetadataSource getAttributes [sRoleList={}]", gson.toJson(sRoleList));
for(SRole sRole : sRoleList) {
tempPermissionList.add(sRole.getRole());
}
//徑獲取資源權限列表
List<SPermission> sPermissionList = securityDataService.findSPermissionListBySPermissionUrl(requestUrl);
logger.info("VFilterInvocationSecurityMetadataSource getAttributes [sPermissionList={}]", gson.toJson(sPermissionList));
for(SPermission sPermission : sPermissionList) {
tempPermissionList.add(sPermission.getPermission());
}
//如果沒有權限控制的url,可以設置都可以訪問,也可以設置默認不許訪問
if(tempPermissionList.isEmpty()) {
return null;//都可以訪問
//tempPermissionList.add("DEFAULT_FORBIDDEN");//默認禁止
}
String[] permissionArray = tempPermissionList.toArray(new String[0]);
logger.info("VFilterInvocationSecurityMetadataSource getAttributes [permissionArray={}]", gson.toJson(permissionArray));
return SecurityConfig.createList(permissionArray);
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
package com.veiking.sec.authorization;
import java.util.Collection;
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.Component;
/**
* 權限管理判斷器|校驗用戶是否有權限訪問請求資源
* @author Veiking
*/
@Component
public class VAccessDecisionManager implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException {
//當前用戶所具有的權限
Collection<? extends GrantedAuthority> userAuthorityList = authentication.getAuthorities();
//訪問資源所需的權限信息
Collection<ConfigAttribute> needAuthoritieList = configAttributes;
//依次循環對比,發現有匹配的即返回
for(ConfigAttribute needAuthoritie: needAuthoritieList) {
String needAuthoritieStr = needAuthoritie.getAttribute();
for (GrantedAuthority userAuthority : userAuthorityList) {
String userAuthorityStr = userAuthority.getAuthority();
if (needAuthoritieStr.equals(userAuthorityStr)) {
return;
}
}
}
//執行到這里說明沒有匹配到應有權限
throw new AccessDeniedException("權限不足!");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
package com.veiking.sec.authorization;
import java.io.IOException;
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 javax.servlet.annotation.WebFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.ServletComponentScan;
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.stereotype.Component;
/**
* 訪問鑒權過濾器
* 該過濾器的作用就是,用戶請求時,提供權限資源管理器和權限判斷器工作的場所,實現鑒權操作
* @author Veiking
*/
@Component
@ServletComponentScan
@WebFilter(filterName="vFilterSecurityInterceptor",urlPatterns="/*")
public class VFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
@Autowired
private VFilterInvocationSecurityMetadataSource vFilterInvocationSecurityMetadataSource;
@Autowired
public void setMyAccessDecisionManager(VAccessDecisionManager vAccessDecisionManager) {
super.setAccessDecisionManager(vAccessDecisionManager);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
FilterInvocation filterInvocation = new FilterInvocation(request, response, chain);
invoke(filterInvocation);
}
public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
// filterInvocation里面有一個被攔截的url
// 里面調用VFilterInvocationSecurityMetadataSource的getAttributes(Object object)這個方法獲取filterInvocation對應的所有權限
// 再調用VAccessDecisionManager的decide方法來校驗用戶的權限是否足夠
InterceptorStatusToken interceptorStatusToken = super.beforeInvocation(filterInvocation);
try {
// 執行下一個攔截器
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
} finally {
super.afterInvocation(interceptorStatusToken, null);
}
}
@Override
public void destroy() {
}
@Override
public Class<?> getSecureObjectClass() {
return FilterInvocation.class;
}
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.vFilterInvocationSecurityMetadataSource;
}
}
版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。
工作时间:8:00-18:00
客服电话
电子邮件
admin@qq.com
扫码二维码
获取最新动态