springBoot+security+mybatis 实现用户权限的数据库动态管理

[b][size=large]一、Spring Security 应用的概述[/size][/b]

[size=medium] 鉴于目前微服务的兴起,Spring周边方案的普及,以及 Spring Security 强大的和高度可定制的优良特性,最近关注了一下相关内容,顺便留个笔记心得,希望对大家有所帮助。[/size]

[size=medium] Spring Security 权限方案针对不同场景需求有多种不同的使用方法,在此,我们最终描述的是如何采用数据库存储配置,并通过自定义过滤器的实现方式,来进行对权限的权利,希望这个过程能加深对Spring Security的理解,如有初学者阅读,建议先简单了解下Spring Security 框架,以避免遭遇太多的疑惑。[/size]

[size=medium] 先说大概,Spring Security,包括绝大部分的安全框架,都可以简单理解为两个核心:一个是认证,即看看这个请求用户存在不存在啊,密码对不对啊等,认证,来确保请求用户的合法性;另一个就是鉴权,即看看这个访问的资源,有没有权限,这个决定用户能做什么,不能做什么。敲黑板,两个重点核心:认证!鉴权!下面,我们将尝试下,看看在 Spring Security 框架内是如何完成这些功能的。[/size]

[size=medium] 在这里,我们不准备剖析 Spring Security 底层的基本逻辑,有些还需要就源码进行解读,这里只讲应用层面的东西。 [/size]

[size=medium] 先说认证,与本次实现密切相关的几个类或接口,是UserDetails、UserDetailsService、AuthenticationProvider,我们可以这么理解:UserDetails是用来封装用户的,用户的帐号信息啊、一些权限啊,帐号状态啊等信息,从数据库那里拿到,首先是要封装成UserDetails的样子,才可以在Spring Security框架中使用的;UserDetailsService,顾名思义,处理UserDetails的Service,它是提供去查询账号信息并封装成UserDetails的服务;AuthenticationProvider的主要工作是负责认证,从登录请求那里拿到帐号密码之类,然后再跟从数据库资源那里得到的UserDetails进行对比确认,如果发现不对劲儿,该报错报错,该提示提示,如果OK,则把这些信息揉巴成一团,封装成一个包含所有信息的认证对象,交给 Spring Security 框架进行管理,供后边有需要的时候随时取用。[/size]

[size=medium] 接下来说鉴权,Spring Security 的鉴权方式有多种,我们大概捋一下,这里我们重点讲述如何通过自定义过滤器的鉴权方式,来实现数据库配置权限的动态管理,与此密切相关的几个核心类或接口分别是:AbstractSecurityInterceptor(Filter)、FilterInvocationSecurityMetadataSource和AccessDecisionManager。我们可以这么理解,FilterInvocationSecurityMetadataSource是权限资源管理器,它的主要工作就是根据请求的资源(路径),从数据库获取相对应的权限信息;AccessDecisionManager类似权限管理判断器,负责校验当前认证用户的权限,是否可以访问;AbstractSecurityInterceptor就是前边这两个角色负责表演的地方,拿到访问资源所需的权限,和认证用户的权限,对比,出结果,如果出现对比不成功,分分钟抛要一个拒绝访问的异常,403forbidden了![/size]

[size=medium] 在这里先把这几个类或者接口,默默的混个眼熟,认证相关:UserDetails、UserDetailsService、AuthenticationProvider;鉴权相关:AbstractSecurityInterceptor(Filter)、FilterInvocationSecurityMetadataSource和AccessDecisionManager,谁是干啥的,谁跟谁什么关系,大概就是那么个意思了,也能猜出 Spring Security 是怎么工作的。[/size]

[size=medium] 接下来还会介绍下 Spring Security 的核心配置类:WebSecurityConfigurerAdapter,它的主要职责就是配置配置哪些资源不需要权限限制啊,哪些需要啊等等,以及做一些综合性的配置操作,以及 Spring Security 本身的注册等。[/size]

[size=medium] 以上是 Spring Security 应用的一个概述,目的是有个简单的了解,提前混个眼熟,便于思路连续性的展开。[/size]

[b][size=large]二、springBoot项目初建[/size][/b]

[size=medium] 在eclipse上怎么创建maven项目,我们就不多说了,方式很多种;这里讲,本次 Spring Security 的实现要用到的依赖主要有 Spring MVC、Spring Security、Mybatis、thymeleaf,我们用自己最熟悉的方式建个maven项目,然后修改pom.xml文件如下:[/size]

pom.xml

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0

org.springframework.boot
spring-boot-starter-parent
2.0.1.RELEASE

sec_test
sec
0.0.1-SNAPSHOT
war


UTF-8
2.0.1.RELEASE





org.springframework.boot
spring-boot-starter-web


org.springframework.boot
spring-boot-starter-security


org.springframework.boot
spring-boot-starter-thymeleaf


org.thymeleaf.extras
thymeleaf-extras-springsecurity4


org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.2


mysql
mysql-connector-java
runtime


com.alibaba
druid
1.0.19



com.google.code.gson
gson
2.8.5



org.projectlombok
lombok
1.16.20
provided




${project.name}



maven-compiler-plugin

1.8
1.8



org.springframework.boot
spring-boot-maven-plugin







[size=medium] Spring boot下,各个版本一般都是向下兼容略有不同,在这种简单的应用上基本体现不出太大的差异,我们遵循各自习惯去配置,开心就好,注意pom文件中,除了几个核心的,额外还有gson和lombok的引入,gson是为了方便输出对象日志;lombok是为了省去bean类中set/get方法,这个可以让代码看起来稍微简练些,首次使用需要提前安装下lombok的插件之类,感兴趣的可以自行百度下,也可以根据自己的习惯决定是否使用。[/size]

[size=medium] 接下来我们在 src/main/resources 中创建一个 application.yml 作为springBoot项目的主配置文件,注意,这个.yml和.properties的配置方式,虽各有优劣长短,但功效是一样的,我们这里将采用 .yml 的方式,文件内容如下:[/size]

application.yml

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: ******


[size=medium] 这个配置文件就是设定一下服务端口啊,服务名称啊,还有thymeleaf相关的一些路径配置,以及一些数据源待用的参数,这个文件的配置参数会被系统默认加载,需要时直接取用,很方便。然后在主路径下创建一个含main方法的SecApplication类,做启动入口,如下:[/size]

SecApplication.java

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


[size=medium] 注意加标签@SpringBootApplication,表示这将是按照 Spring boot 项目的形式运行。然后直接右键运行启动,留意下输出窗口,看看什么情况,启动成功,注意,输出栏的日志里很突兀的大了这样一行代码:Using generated security password: XXXX7e44-e83c-460a-aeef-94249316XXXX ,这个是 Spring Security 自带默认的,用户名为user,密码就是这串UUID一样的串儿,接下来,我们浏览器输入:http://localhost:8090,敲回车,自动跳转到了http://localhost:8090/login的路径,我们可以看到一个框架本身自带的登录页面:[/size]

[img]http://dl2.iteye.com/upload/attachment/0130/5484/14df6d4c-a972-37af-897a-4f6763a43f90.png[/img]

[size=medium] 我们在窗口输入默认的用户名密码,提交,就得到了这样一个页面:[/size]

[img]http://dl2.iteye.com/upload/attachment/0130/5486/0562907e-ea8a-3f5a-a395-77abdfd17e16.png[/img]

[size=medium] 好了,初步的 Spring Security 项目验证通过,项目创建完成。[/size]

[b][size=large]三、数据库信息的创建[/size][/b]

[size=medium] 这一波操作我们要创建本次实现要用的数据库表了,按照一般节奏,我们先来五张表:s_user、s_role、s_permission 和 s_user_role、s_role_permission,简单介绍下,就是用户、角色、权限资源和他们的关联关系表,他们结构如下:[/size]

s_user
[img]http://dl2.iteye.com/upload/attachment/0130/5488/ad42d585-2349-3eff-be56-15715c3a16f9.png[/img]
s_role
[img]http://dl2.iteye.com/upload/attachment/0130/5490/395d1b9f-96a4-320a-a865-075280d43027.png[/img]
s_permission
[img]http://dl2.iteye.com/upload/attachment/0130/5492/f3b345f7-f076-38c0-b5bc-89d84c7265f6.png[/img]
s_user_role
[img]http://dl2.iteye.com/upload/attachment/0130/5494/8b6413cd-1684-3fdf-96c4-6a7e456fa744.png[/img]
s_role_permission
[img]http://dl2.iteye.com/upload/attachment/0130/5496/bcc7cb77-3ca8-3bc2-add2-808c965f436f.png[/img]

[size=medium] 我们顺便贴上结构代码,以便使用:[/size]


-- ----------------------------
-- 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;


[size=medium] 接下来我们新增一些用户数据,admin、veiking、xiaoming,添加一些记录,大概意思是,admin拥有所有权限,veiking只有hello、index相关权限,xiaoming什么权限都没有,添加数据记录的脚本如下:[/size]


-- ----------------------------
-- 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页面资源');

[size=medium] 好了,数据库表相关的内容是准备完成。[/size]

[b][size=large]四、测试页面的准备[/size][/b]

[size=medium] 紧接着我们创建一些用来测试检验效果的页面:login.html、index、admin、hello 等页面,其中 login.html 是用来检验登录效果的,代码如下:[/size]

login.html





登录





INDEX
| ADMIN
| HELLO







已成功注销
有错误,请重试

使用用户名密码登录





















[size=medium] index、admin、hello等页面内容都差不多,就是不同导航链接页面,到时候会用来测试权限控制的一些效果,其中 index.html 的内容如下:[/size]

index.html


xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">

主页




INDEX



你好:

INDEX
| ADMIN
| HELLO












[size=medium] 好了,页面也准备好了,接着下一步。[/size]


[b][size=large]五、基础类及查询接口的创建[/size][/b]

[size=medium] 所需数据是准备好了,接下来我们要创建一系列的数据对象,和对应的查询接口,来供 Spring Security 使用,先来创建一波数据 bean 类:SUser、SRole、SPermission,这几个分别是用户、角色、权限资源类,代码如下:[/size]

SUser.java

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();
}
}


SRole.java

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;
}

SPermission.java

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;
}


[size=medium] 注意@Data、@NoArgsConstructor、@AllArgsConstructor这些注解,都是lombok帮助处理set/get和全参无参构造方法的,如果不喜欢,自行替换即可。[/size]

[size=medium] 然后来处理查询接口,我们这里采用的是 mybatis 框架的方式,好了,创建几个对应的dao,代码如下:[/size]

SUserDao.java

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);

}

SRoleDao.java

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 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 findSRoleListBySPermissionUrl(String sPermissionUrl);
}


SPermissionDao.java

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 findSPermissionListBySUserId(int sUserId);

/**
* 根据资源路径获取资源权限列表
* @param sPermissionUrl
* @return
*/
@Select(value=" SELECT * FROM s_permission sp WHERE sp.url = #{sUserId} ")
public List findSPermissionListBySPermissionUrl(String sPermissionUrl);
}



[size=medium] 请注意,这里的几个Dao查询接口是使用注解的方式实现谁,当然,一般mybatis框架通常使用的方式是dao接口+xml脚本,当然个人也是习惯用xml实现较为复杂逻辑的脚本,但是在相对简单逻辑的操作上,直接用注解的方式是清爽的不能再清爽;两者在实际运用中是等效的,也是可以一同使用。[/size]

[size=medium] 这几个接口的主要作用是:通过用户名(登录名)来获取用户信息;通过用户ID、资源路径(请求路径)来获取角色列表和权限资源列表。紧接着,本着编程习惯,我们再搞一个服务接口,将上边几个dao的功能整合,统一对外提供数据服务:[/size]

SecurityDataService.java

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 findSRoleListBySUserId(int sUserId) {
return sRoleDao.findSRoleListBySUserId(sUserId);
}

public List findSRoleListBySPermissionUrl(String sPermissionUrl) {
return sRoleDao.findSRoleListBySPermissionUrl(sPermissionUrl);
}

public List findSPermissionListBySUserId(int sUserId) {
return sPermissionDao.findSPermissionListBySUserId(sUserId);
}

public List findSPermissionListBySPermissionUrl(String sPermissionUrl) {
return sPermissionDao.findSPermissionListBySPermissionUrl(sPermissionUrl);
}
}



[size=medium] 这个service没有额外的操作,仅仅是传递dao的功能,OK,到此,Spring Security 需要用的数据服务等一些准备部分,我们都已经准备好了,下面的环节,就是重点了。[/size]

[b][size=large]六、重点:Spring Security之用户认证[/size][/b]

[size=medium] 经过一番相当罗嗦的铺垫,终于迎来了正题,我们将在接下来的环节里,讲述 Spring Security 认证有关的东西。[/size]

[size=medium] 首先,再次回顾,Spring Security 认证有关的重要类或接口:UserDetails、UserDetailsService、AuthenticationProvider,我们将尝试自定义封装UserDetails,经由UserDetailsService提供给AuthenticationProvider,然后和请求消息中获取的用户信息进行对比认证。[/size]

[size=medium] 首先,为了刻意的来区分认证和鉴权这里啊范畴,我们先来卖个关子,在包主路径下创建俩包:authentication、authorization,这俩单词简直是很像了,也是特意才用这两个单词,是看到有位前辈在博客中调侃了他们,印象深刻:authentication即认证,authorization即鉴权,注意字母微小的差异下在逻辑实现中不同的含义。[/size]

[size=medium] 好,在authentication包下来完成我们 Spring Security 的认证,先新建一个 VUserDetails 类来实现 UserDetails(注:在此,所有的重新实现,都将在原类或接口名称前缀加大写的V,此处仅为示例,如有仿例操作,请根据个人习惯;包括之前的类或接口名,也不是很符合java推荐的命名规则,这只是为了在名称上强调而强调,勿在意,更勿仿效),代码如下:[/size]

VUserDetails.java

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 sRoleList = null;
//用户资源权限列表
private List sPermissionList = null;
/**
* 注意后边的这两个参数:sRoleList、sPermissionList
* @param sUser
* @param sRoleList
* @param sPermissionList
*/
public VUserDetails(SUser sUser, List sRoleList, List sPermissionList) {
super(sUser);
this.sRoleList = sRoleList;
this.sPermissionList = sPermissionList;
}
/**
* 获取用户权限列表方法
* 可以理解成,返回了一个List,之后所谓的权限控制、鉴权,其实就是跟这个list里的String进行对比
* 这里处理了角色和资源权限两个列表,可以这么理解,
* 角色是权限的抽象集合,是为了更方便的控制和分配权限,而真正颗粒化细节方面,还是需要资源权限自己来做
*/
@Override
public Collection getAuthorities() {
StringBuilder authoritiesBuilder = new StringBuilder("");
List tempRoleList = this.getsRoleList();
if (null != tempRoleList) {
for (SRole role : tempRoleList) {
authoritiesBuilder.append(",").append(role.getRole());
}
}
List 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 getsRoleList() {
return sRoleList;
}

public void setsRoleList(List sRoleList) {
this.sRoleList = sRoleList;
}

public List getsPermissionList() {
return sPermissionList;
}

public void setsPermissionList(List sPermissionList) {
this.sPermissionList = sPermissionList;
}

}


[size=medium] 注意这个VUserDetails,它继承SUser并实现了UserDetails,这个类主要功能就是封装用户信息,包括从SUser继承来的用户名密码等属性,还有两个角色和权限的列表,注意这个 getAuthorities(),这个方法主要工作是提供一组框架定义的权限列表,可以留意下源码,这个并没有定义具体类型,我们这里就用String类型实现这个权限。[/size]

[size=medium] 这里还要解释下,我们在getAuthorities方法里里分别循环了两个列表来加工 Spring Security 需要权限信息,即 tempRoleList 和 tempPermissionList,可以这样子理解,角色和权限的概念,角色本身是权限的抽象集合,是协助我们开发管理的东西,真正意义的东西还是颗粒细小的权限。添个插曲,在本人最初接触到权限设计的时候,总是傻傻的被二者的关系搞晕,加上一些实际应用的系统还乐此不疲的在权限命名上"ROLE"来"ROLE"去的,甚至一些方法命名本身也在混淆这二者(怀疑可能是英语的使用习惯之类的原因),导致早先的我常常常常陷入对二者的理解困惑上,当然现在清晰的很多: 在大块儿整体性的权限控制上,角色控制为主;细化到页面小块儿、按钮级别的,权限控制为主;一般再加上访问URL的过滤鉴权,基本上一套强壮的权限控制体系是稳稳的在这儿了。[/size]

[size=medium] 最后注意下代码里的几个isXXX方法,这些是一些细节补充,一般默认,也可以重写控制下逻辑;紧接着我们新建一个 VUserDetailsService 类,来实现UserDetailsService,代码如下:[/size]

VUserDetailsService.java

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 sRoleList = securityDataService.findSRoleListBySUserId(sUser.getId());
//用户资源权限列表
List sPermissionList = securityDataService.findSPermissionListBySUserId(sUser.getId());
return new VUserDetails(sUser, sRoleList, sPermissionList);
}

}


[size=medium] 这个类基本上没啥好说的,服务提供者,就是一个搬运工,看这个loadUserByUsername()方法,拿到用户基本信息、角色列表和资源权限列表后,构造一个 VUserDetails 对象,OK返回。接下来是一个小重点,我们创建一个 VAuthenticationProvider 类来实现 AuthenticationProvider,代码如下:[/size]

VAuthenticationProvider.java

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;
}

}


[size=medium] 这个实现类的核心就是authenticate方法,一步步看,系统会将用户在登录请求操作的时候,把输入的用户名密码等,封装到一个Authentication对象中,我们从这个对象里拿到用户名,通过 VUserDetailsService 获取到 VUserDetails 对象,然后拿这个对象的密码属性,和请求Authentication对象中获取的密码进行对比,如果一切OK,验证功过,然后再将这些所有信息,整体封装成一个Authentication对象(这里边我们用的是UsernamePasswordAuthenticationToken),交给系统框架,后期可以随时取用。[/size]

[size=medium] 好了,经过上面的工作,用户认证的逻辑已经完事儿了,我们要做访问工作,这里还要做些配置操作,这里分别新建俩类,代码如下:[/size]


WebMvcConfig.java

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");
}
}

PageController.java

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



[size=medium] WebMvcConfig 是一个简单的路径映射,功能跟在 PageController中实现的差不多,之所以多写一个PageController,是因为后边会有其他的功能演示。[/size]

[size=medium] 然后我们还需创建一个 WebSecurityConfig 类来继承 WebSecurityConfigurerAdapter,代码如下:[/size]

WebSecurityConfig.java

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();
}
}



[size=medium] 这个类是使用 Spring Security 的主配置入口,在这个配置文件中,正式启用 Spring Security 包括我们之前所讲的所有功能,这里主要留意一下负责安全策略配置的 configure()方法,这个方法里可以定义登录登出等操作细节,以及一些静态资源的权限忽略之类的,甚至也是可以直接手动配权限的。[/size]

[size=medium] 一切完事儿,我们运行 SecApplication ,开始验证之旅:[/size]

[img]http://dl2.iteye.com/upload/attachment/0130/5498/a7d068c6-abd4-37b7-a5fd-b643c5f05115.png[/img]

[size=medium] 在登录页面,输入用户名密码:admin/admin,登录看看,随便点点跳跳,换成veiking/veiking试试,也可以输错试试,再试下登出:[/size]

[img]http://dl2.iteye.com/upload/attachment/0130/5500/7916b4d5-9123-3981-b86b-1fb35486d8ce.png[/img]
[img]http://dl2.iteye.com/upload/attachment/0130/5502/990e7216-b53d-3b12-ab16-b44b1312d088.png[/img]
[img]http://dl2.iteye.com/upload/attachment/0130/5504/f1513035-e8d9-3462-a031-51e0be5be2aa.png[/img]

[size=medium] 好了,这个简单的用户认证功能看来是可以了,我们接下来看看如何控制权限。[/size]

[b][size=large]七、重点:Spring Security之鉴权-初试[/size][/b]

[size=medium] 认证OK,回想下,是否还记得,在VAuthenticationProvider的校验环节,我们在封装返回给系统的Authentication对象里,是提供了vUserDetails.getAuthorities()这个认证列表的,接下来看看,这个被交给系统的认证列表,是怎么体现的。[/size]

[size=medium] 我们打开 hello.html 页面,在其中的几个导航跳转的信息上,添加一个 sec:authorize="hasAuthority('XXX')" 的代码,这样子的脚本,大概意思就是,只有名为‘XXX’的角色或者权限的用户,登录之后才可以看到,如下:[/size]

hello.html


xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">


HELLO




HELLO



你好:

INDEX
| ADMIN
| HELLO












(注意,在页面中使用 Spring Security 相关脚本,要在标签处添加 xmlns:th="http://www.thymeleaf.org" 、 xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4" 等约束规范)重新启动后,分别用不同的用户,登录后跳转到hello页面查看,这时候可以看到,admin用户拥有较多权限,都可以看到, veiking 用户只能看到index和hello导航,而 xiaoming 用户什么都看不到了,并且他们都不能看到注销按钮,就是这个效果:[/size]

[img]http://dl2.iteye.com/upload/attachment/0130/5506/932eef0f-1a57-344b-be21-31300a9c4337.png[/img]
[img]http://dl2.iteye.com/upload/attachment/0130/5508/88ba11a8-0483-3c76-9c79-231e3b02980c.png[/img]

[size=medium] 上边是从页面层面来进行权限控制的,注意hasAuthority('XXX')中,有用到R_ADMIN、P_ADMIN、P_HELLO不同类型的权限字眼,包含角色和权限,这个控制的颗粒度没有绝对的,只要设计成规律可循、操作可行方案即可。[/size]

[size=medium] 接下来,打开 PageController,在/admin处添加标签:@PreAuthorize("hasAuthority('R_ADMIN')"),如下:[/size]

PageController.java

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



[size=medium] 注意,这个操作还需要在 WebSecurityConfig 类中加 @EnableGlobalMethodSecurity(prePostEnabled=true) 标签来,开启注解控制权限,然后配置 authenticationManagerBean 以供支持,代码如下:[/size]

WebSecurityConfig.java

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();
}
}



[size=medium] 然后再次启动,用veiking登录,INDEX页面点击ADMIN导航 ——好,403 Forbidden了,对,被拦了,就是这个效果。[/size]

[size=medium] 以上这些,是简单的对 Spring Security 鉴权操作的一些尝试,当然,如果是小规模功能开发,这些是可以满足的,如果想追求更为灵活的控制,就要重新是实现下过滤机制,接下来我们就尝试下从对数据库层面的配置,实现权限的动态管理。[/size]

[b][size=large]八、重点:Spring Security之鉴权-过滤器[/size][/b]

[size=medium] 上边我们已尝试了经通过页面脚本和注解这两种方式的权限控制,接下来,我们尝试下通过数据库的权限配置,来过滤用户操作请求的。[/size]

[size=medium] 跟认证对应,我们新建一个包,authorization,然后在这个里面来实现过滤请求方式的鉴权:先写一个 VFilterInvocationSecurityMetadataSource 类,来实现 FilterInvocationSecurityMetadataSource,这个可以简单理解成权限资源管理器,它的工作是通过用户的请求地址,来获取访问这个地址所需的权限,代码如下:[/size]

FilterInvocationSecurityMetadataSource.java

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 getAttributes(Object object) throws IllegalArgumentException {
//获取请求起源路径
String requestUrl = ((FilterInvocation) object).getRequestUrl();
logger.info("VFilterInvocationSecurityMetadataSource getAttributes [requestUrl={}]", requestUrl);
//登录页面就不需要权限
if ("/login".equals(requestUrl)) {
return null;
}
//用来存储访问路径需要的角色或权限信息
List tempPermissionList = new ArrayList();
//获取角色列表
List sRoleList = securityDataService.findSRoleListBySPermissionUrl(requestUrl);
logger.info("VFilterInvocationSecurityMetadataSource getAttributes [sRoleList={}]", gson.toJson(sRoleList));
for(SRole sRole : sRoleList) {
tempPermissionList.add(sRole.getRole());
}
//径获取资源权限列表
List 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 getAllConfigAttributes() {
// TODO Auto-generated method stub
return null;
}

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

}


[size=medium] 接着访问需要的权限资源拿到了,就得有判断的地方,新建一个 VAccessDecisionManager 来实现 AccessDecisionManager ,这个类可以理解成权限管理判断器,他的主要工作就是鉴权,通过拿到的访问路径所需的权限,和用户所拥有的权限进行对比,判断用户是否有权限访问,代码如下:[/size]

VAccessDecisionManager.java

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 configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException {
//当前用户所具有的权限
Collection userAuthorityList = authentication.getAuthorities();
//访问资源所需的权限信息
Collection 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;
}

}


[size=medium] 最后,要写一个过滤器,提供上边这些功能的工作场所,创建 VFilterSecurityInterceptor 类,继承 AbstractSecurityInterceptor 并实现 Filter,这就是个鉴权过滤器,代码如下:[/size]

VFilterSecurityInterceptor.java

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;
}

}


[size=medium] 这里边注意 @ServletComponentScan 和 @WebFilter(filterName="vFilterSecurityInterceptor",urlPatterns="/*") 注解,这个是确保过滤器自动注册并工作,否则过滤器无效。[/size]

[size=medium] 接下来启动项目,用个个用户登进去看看,尤其是veiking和xiaoming用户,访问没有权限的连接时果断遭到限制,403 Forbidden![/size]

[img]http://dl2.iteye.com/upload/attachment/0130/5510/5d650109-06c4-3eb3-a8cc-369acb1c7223.png[/img]

[b][size=large]九、结语[/size][/b]

[size=medium] 好了,经过这么一番折腾,学习应用 Spring Security 框架该接触到的一些知识点,都有所体现了。权限控制的本质,就是对比校验,其一般体现方式,就是过滤器。Spring Security 是提供了一种相对比较好的表现形式,给出了一个优良的范式,敲示例代码的本身,更重要的应该是为了帮助理解和学习,而不是为了实现而实现。[/size]

[size=medium] 本文是足够罗嗦,也是个人为了加深在理解记忆,但有些地方甚至也是错误的、不合乎规范的,希望大家不要被误导,这只能说是一个提供理解的参考,帮助大家从懵懂到懂;还有需要注意的是,因spring版本不同导致的一些细节差异,可能会有小坑,还是需要注意下的。文中所涉及代码最后都在附件中,感兴趣的同学可以自行下载。[/size]


[size=medium] 还有,喜欢的,扫下支付宝家电红包吧,哈哈哈![/size]

[img]http://dl2.iteye.com/upload/attachment/0130/5513/25e7d164-24ca-3621-a0de-388ac2eacda5.jpg[/img]


代码附件:
[url]http://dl2.iteye.com/upload/attachment/0130/5515/3b1eeefa-fa64-3dd2-97c0-753bd96c1acc.rar[/url]

你可能感兴趣的:(Java,Spring,Maven,SpringMVC,MyBatis,MySQL,Springboot,Security)