Spring Security(7)
您好,我是湘王,这是我的博客园,欢迎您来,欢迎您再来~
有时某些业务或者功能,需要在用户请求到来之前就进行一些判断或执行某些动作,就像在Servlet中的FilterChain过滤器所做的那样,Spring Security也有类似机制。Spring Security有三种增加过滤器的方式:addFilterBefaore()、 addFilterAt()和addFilterAfter(),也可以disable掉默认的过滤器,例如:
1、http.logout().disable();或者http.headers().disable();
2、用自定义过滤器http.addFilterAt(new MyLogoutFilter(), LogoutFilter.class)替换
Spring Security的官方列出了过滤器调用顺序,具体可参考官方网站。
Spring Security已经定义好,可以直接使用的过滤器有下面这些:

比如,现在的互联网应用都有一个通用的「业务规则」是:在执行所有功能接口的时候都要检查确认接口签名的有效性。所谓接口签名其实就是一套进入准则,客户端按照服务器规定的方式向服务器证明自己确实是某个网站的用户。
那么,在Spring Security里面可以这么干:
/**
* 自定义拦截过滤器
*
* @author 湘王
*/
@Component
public class CustomInterceptorFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException {
// 保存参数=参数值对
Map<String, String> mapResult = new HashMap<String, String>();
// 请求中的参数
Enumeration<String> em = request.getParameterNames();
while (em.hasMoreElements()) {
String paramName = em.nextElement();
String value = request.getParameter(paramName);
mapResult.put(paramName, value);
}
// 验证参数,只要有一个不满足条件,立即返回
if (null == mapResult.get("platform") ||
null == mapResult.get("timestamp") ||
null == mapResult.get("signature")) {
response.getWriter().write("api validate failure");
}
Object result = null;
String platform = mapResult.get("platform");
String timestamp = mapResult.get("timestamp");
String signature = mapResult.get("signature");
// 后端生成签名:platform = "xiangwang" timestamp = "159123456789" signature = ""
String sign = DigestUtils.md5DigestAsHex((platform + timestamp).getBytes());
validateSignature(signature, sign, request, response, chain);
} // 验证签名
private void validateSignature(String signature, String sign, ServletRequest request,
ServletResponse response, FilterChain chain) throws IOException {
if (signature.equalsIgnoreCase(sign)) {
try {
// 让调用链继续往下执行
chain.doFilter(request, response);
} catch (Exception e) {
response.getWriter().write("api validate failure");
}
} else {
response.getWriter().write("api validate failure");
}
} public static void main(String[] args) {
// 这里的验证签名算法可以随便自定义实现(guid = "0" platform = "web" timestamp = 156789012345)
// 下面的代码只是伪代码,举个例子而已
String sign = "";
if(StringUtils.isBlank(guid)) {
// 首次登录,后端 platform + timestamp + "xiangwang" 生成签名
sign = DigestUtils.md5DigestAsHex((platform + timestamp + "xiangwang").getBytes());
validateSignature(signature, sign, request, response, chain);
} else {
// 不是首次登录,后端 guid + platform + timestamp 生成签名
// 从Redis拿到token,这里不实现
// Object object = service.getObject("token#" + guid);
Object object = "1234567890abcdefghijklmnopqrstuvwxyz";
if(null == object) {
response.getWrite().write("token expired");
} else {
token = (String) obejct;
// 验证sign
sign = DigestUtils.md5DigestAsHex((platform + timestamp + "xiangwang").getBytes());
validateSignature(signature, sign, request, response, chain);
}
}
System.out.println(DigestUtils.md5DigestAsHex(("web" + "156789012345").getBytes()));
}
}
然后修改WebSecurityConfiguration,加入刚才自定义的「过滤器」:
// 控制逻辑
@Override
protected void configure(HttpSecurity http) throws Exception {
// 执行UsernamePasswordAuthenticationFilter之前添加拦截过滤
http.addFilterBefore(new CustomInterceptorFilter(), UsernamePasswordAuthenticationFilter.class); http.authorizeRequests()
.anyRequest().authenticated()
// 设置自定义认证成功、失败及登出处理器
.and().formLogin().loginPage("/login")
.successHandler(successHandler).failureHandler(failureHandler).permitAll()
.and().logout().logoutUrl("/logout").deleteCookies("JSESSIONID")
.logoutSuccessHandler(logoutSuccessHandler).permitAll()
// 配置无权访问的自定义处理器
.and().exceptionHandling().accessDeniedHandler(accessDeniedHandler)
// 记住我
.and().rememberMe()
// 数据库保存,这种方式在关闭服务之后仍然有效
.tokenRepository(persistentTokenRepository())
// 默认的失效时间会从用户最后一次操作开始计算过期时间,过期时间最小值就是60秒,
// 如果设置的值小于60秒,也会被更改为60秒
.tokenValiditySeconds(30 * 24 * 60 * 60)
.userDetailsService(customUserDetailsService)
.and().cors().and().csrf().disable();
}
运行postman测试后的效果为:

增加了接口需要的签名参数。
在前面的内容中,几乎没有对Spring Security真正的核心功能,也就是认证授权做什么说明,也只是简单演示了一些admin角色登录。那么在做完前面这些铺垫之后,就需要接着来说说这一块了。
先创建创建sys_permission表,为认证授权的细化做准备:

同样,需要创建实体类和Service类。
/**
* 权限entity
*
* @author 湘王
*/
public class SysPermission implements Serializable, RowMapper<SysPermission> {
private static final long serialVersionUID = 4121559180789799491L; private int id;
private int roleid;
private String path;
private String permission;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
protected Date createtime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
protected Date updatetime; public int getId() {
return id;
} public void setId(int id) {
this.id = id;
} public int getRoleid() {
return roleid;
} public void setRoleid(int roleid) {
this.roleid = roleid;
} public String getPath() {
return path;
} public void setPath(String path) {
this.path = path;
} public String getPermission() {
return permission;
} public void setPermission(String permission) {
this.permission = permission;
} public Date getCreatetime() {
return createtime;
} public void setCreatetime(Date createtime) {
this.createtime = createtime;
} public Date getUpdatetime() {
return updatetime;
} public void setUpdatetime(Date updatetime) {
this.updatetime = updatetime;
} @Override
public SysPermission mapRow(ResultSet result, int i) throws SQLException {
SysPermission permission = new SysPermission(); permission.setId(result.getInt("id"));
permission.setRoleid(result.getInt("roleid"));
permission.setPath(result.getString("path"));
permission.setPermission(result.getString("permission"));
permission.setCreatetime(result.getTimestamp("createtime"));
permission.setUpdatetime(result.getTimestamp("updatetime")); return permission;
}
}
/**
* 权限Service
*
* @author 湘王
*/
@Service
public class PermissionService {
@Autowired
private MySQLDao mySQLDao; // 得到某个角色的全部权限
public List<SysPermission> getByRoleId(int roleid) {
String sql = "SELECT id, url, roleid, permission, createtime, updatetime FROM sys_permission WHERE roleid = ?";
return mySQLDao.find(sql, new SysPermission(), roleid);
}
}
再在LoginController中增加几个hasPermission()方法:
// 细化权限
@GetMapping("/admin/create")
@PreAuthorize("hasPermission('/admin', 'create')")
public String adminCreate() {
return "admin有ROLE_ADMIN角色的create权限";
} @GetMapping("/admin/read")
@PreAuthorize("hasPermission('/admin', 'read')")
public String adminRead() {
return "admin有ROLE_ADMIN角色的read权限";
} @GetMapping("/manager/create")
@PreAuthorize("hasPermission('/manager', 'create')")
public String managerCreate() {
return "manager有ROLE_MANAGER角色的create权限";
} @GetMapping("/manager/remove")
@PreAuthorize("hasPermission('/manager', 'remove')")
public String managerRemove() {
return "manager有ROLE_MANAGER角色的remove权限";
}
再来实现对hasPermission()方法的处理,也就是自定义权限处理的过滤器:
/**
* 自定义权限处理
*
* @author 湘王
*/
@Component
public class CustomPermissionEvaluator implements PermissionEvaluator {
@Autowired
private RoleService roleService;
@Autowired
private PermissionService permissionService; @Override
public boolean hasPermission(Authentication authentication, Object targetUrl, Object permission) {
// 获得loadUserByUsername()方法的结果
User user = (User) authentication.getPrincipal();
// 获得用户授权
Collection<GrantedAuthority> authorities = user.getAuthorities(); // 遍历用户所有角色
for(GrantedAuthority authority : authorities) {
String roleName = authority.getAuthority();
int roleid = roleService.getByName(roleName).getId();
// 得到角色所有的权限
List<SysPermission> permissionList = permissionService.getByRoleId(roleid);
if (null == permissionList) {
continue;
} // 遍历permissionList
for(SysPermission sysPermission : permissionList) {
String pstr = sysPermission.getPermission();
String path = sysPermission.getPath();
// 判空
if (StringUtils.isBlank(pstr) || StringUtils.isBlank(path)) {
continue;
}
// 如果访问的url和权限相符,返回true
if (path.equals(targetUrl) && pstr.equals(permission)) {
return true;
}
}
} return false;
} @Override
public boolean hasPermission(Authentication authentication, Serializable serializable,
String targetUrl, Object permission) {
return false;
}
}
最后,再把自定义的CustomPermissionEvaluator注册到WebSecurityConfiguration中去,也就是在WebSecurityConfiguration中加入下面的代码:
// 注入自定义PermissionEvaluator
@Bean
public DefaultWebSecurityExpressionHandler webSecurityExpressionHandler() {
DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
handler.setPermissionEvaluator(permissionEvaluator);
return handler;
}
运行postman进行测试,注意:启动时要在配置文件中加入下面这个配置:
spring.main.allow-bean-definition-overriding=true
从结果可以看到:

感谢您的大驾光临!咨询技术、产品、运营和管理相关问题,请关注后留言。欢迎骚扰,不胜荣幸~
Spring Security(7)的更多相关文章
- Spring Security(08)——intercept-url配置
http://elim.iteye.com/blog/2161056 Spring Security(08)--intercept-url配置 博客分类: spring Security Spring ...
- Spring Security(三)
Spring Security(三) 个性化用户认证流程 自定义登录页面 在配置类中指定登录页面和接收登录的 url @Configuration public class BrowserSecuri ...
- Spring Security(二)
Spring Security(二) 注:凡是源码部分,我已经把英文注释去掉了,有兴趣的同学可以在自己项目里进去看看.:-) 定义用户认证逻辑 用户登录成功后,用户的信息会被 Security 封装在 ...
- Spring Security(一)
Spring Security(一) 基本原理 前言 Spring Security核心功能 认证(你是谁) 授权(你能干什么) 攻击防护(防止伪造身份) Srping Security基本原理 项目 ...
- 【权限管理系统】Spring security(三)---认证过程(原理解析,demo)
在前面两节Spring security (一)架构框架-Component.Service.Filter分析和Spring Security(二)--WebSecurityConfigurer配 ...
- SpringBoot集成Spring Security(7)——认证流程
文章目录 一.认证流程 二.多个请求共享认证信息 三.获取用户认证信息 在前面的六章中,介绍了 Spring Security 的基础使用,在继续深入向下的学习前,有必要理解清楚 Spring Sec ...
- SpringBoot集成Spring Security(6)——登录管理
文章目录 一.自定义认证成功.失败处理 1.1 CustomAuthenticationSuccessHandler 1.2 CustomAuthenticationFailureHandler 1. ...
- SpringBoot集成Spring Security(5)——权限控制
在第一篇中,我们说过,用户<–>角色<–>权限三层中,暂时不考虑权限,在这一篇,是时候把它完成了. 为了方便演示,这里的权限只是对角色赋予权限,也就是说同一个角色的用户,权限是 ...
- SpringBoot集成Spring Security(4)——自定义表单登录
通过前面三篇文章,你应该大致了解了 Spring Security 的流程.你应该发现了,真正的 login 请求是由 Spring Security 帮我们处理的,那么我们如何实现自定义表单登录呢, ...
- SpringBoot集成Spring Security(2)——自动登录
在上一章:SpringBoot集成Spring Security(1)——入门程序中,我们实现了入门程序,本篇为该程序加上自动登录的功能. 文章目录 一.修改login.html二.两种实现方式 2. ...
随机推荐
- vue3中defineComponent 的作用
vue3中,新增了 defineComponent ,它并没有实现任何的逻辑,只是把接收的 Object 直接返回,它的存在是完全让传入的整个对象获得对应的类型,它的存在就是完全为了服务 TypeSc ...
- ProxySQL介绍
介绍 ProxySQL是用C++语言开发的,一个轻量级开源软件,性能和功能满足读写中间件所需的绝大多数功能,其配置数据基于SQLite存储,目前已到v2.4.1版本. 功能方面如下: 最基本的读/写分 ...
- 使用KVM安装windows10系统出现内存直接占满的情况解决
情况说明: 在使用kvm安装windows10系统的时候,采用的win10系统不是原版系统,而是经过进一步封装的系统,使用大白菜PE先格式化磁盘,然后再安装的系统,在系统安装好重启的时候,卡在安装界面 ...
- redis监控规则
其他说明参考host主机监控规则:https://www.cnblogs.com/sanduzxcvbnm/p/13589848.html groups: - name: Redis monitori ...
- STM32F10x SPL V3.6.2 集成 FreeRTOS v202112
STM32F10x SPL 集成 FreeRTOS 在整理 GCC Arm 工具链的Bluepill代码示例, 常用外设都差不多了, 接下来是 FreeRTOS, 网上查到的基本上都是基于旧版本的集成 ...
- 「国产系统」Tubian 0.3,兼容Windows和Android的GNU/Linux系统!
0.4版已发布:https://www.cnblogs.com/tubentubentu/p/16741197.html Sourceforge.net主页(提供下载):https://sourcef ...
- [题解] Codeforces 1268 D Invertation in Tournament 结论,兰道定理
题目 本题需要用到的结论: 一.兰道定理 二.如果\(n\geq4\),那么\(n\)个点的强连通竞赛图存在\(n-1\)个点的强连通子图. 证明: 现在有一个n-1个点的竞赛图(不一定强连通,称其为 ...
- MySQL之安装(linux两种版本版本安装)
LinuxMySQL安装(Mysql5.5版本) 第一种 有安装包的安装方式 1.下载地址: http://dev.mysql.com/downloads/mysql 2.检查当前系统是否安装过mys ...
- hive之数据导入导出
hive数据导入导出 一.导入数据4种方式 建表语句 create table test( name string, friends array, children map<string, in ...
- Codeforces Round #804 (Div. 2) C(组合 + mex)
Codeforces Round #804 (Div. 2) C(组合 + mex) 本萌新的第一篇题解qwq 题目链接: 传送门QAQ 题意: 给定一个\(\left [0,n-1 \right ] ...