您好,我是湘王,这是我的博客园,欢迎您来,欢迎您再来~

有时某些业务或者功能,需要在用户请求到来之前就进行一些判断或执行某些动作,就像在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)的更多相关文章

  1. Spring Security(08)——intercept-url配置

    http://elim.iteye.com/blog/2161056 Spring Security(08)--intercept-url配置 博客分类: spring Security Spring ...

  2. Spring Security(三)

    Spring Security(三) 个性化用户认证流程 自定义登录页面 在配置类中指定登录页面和接收登录的 url @Configuration public class BrowserSecuri ...

  3. Spring Security(二)

    Spring Security(二) 注:凡是源码部分,我已经把英文注释去掉了,有兴趣的同学可以在自己项目里进去看看.:-) 定义用户认证逻辑 用户登录成功后,用户的信息会被 Security 封装在 ...

  4. Spring Security(一)

    Spring Security(一) 基本原理 前言 Spring Security核心功能 认证(你是谁) 授权(你能干什么) 攻击防护(防止伪造身份) Srping Security基本原理 项目 ...

  5. 【权限管理系统】Spring security(三)---认证过程(原理解析,demo)

      在前面两节Spring security (一)架构框架-Component.Service.Filter分析和Spring Security(二)--WebSecurityConfigurer配 ...

  6. SpringBoot集成Spring Security(7)——认证流程

    文章目录 一.认证流程 二.多个请求共享认证信息 三.获取用户认证信息 在前面的六章中,介绍了 Spring Security 的基础使用,在继续深入向下的学习前,有必要理解清楚 Spring Sec ...

  7. SpringBoot集成Spring Security(6)——登录管理

    文章目录 一.自定义认证成功.失败处理 1.1 CustomAuthenticationSuccessHandler 1.2 CustomAuthenticationFailureHandler 1. ...

  8. SpringBoot集成Spring Security(5)——权限控制

    在第一篇中,我们说过,用户<–>角色<–>权限三层中,暂时不考虑权限,在这一篇,是时候把它完成了. 为了方便演示,这里的权限只是对角色赋予权限,也就是说同一个角色的用户,权限是 ...

  9. SpringBoot集成Spring Security(4)——自定义表单登录

    通过前面三篇文章,你应该大致了解了 Spring Security 的流程.你应该发现了,真正的 login 请求是由 Spring Security 帮我们处理的,那么我们如何实现自定义表单登录呢, ...

  10. SpringBoot集成Spring Security(2)——自动登录

    在上一章:SpringBoot集成Spring Security(1)——入门程序中,我们实现了入门程序,本篇为该程序加上自动登录的功能. 文章目录 一.修改login.html二.两种实现方式 2. ...

随机推荐

  1. Linux病毒扫描工具ClamAV(Clam AntiVirus)安装使用

    在线检测木马病毒的网址:https://www.virustotal.com/gui/home/upload 一.简介 ClamAV(Clam AntiVirus)是Linux平台上的开源病毒扫描程序 ...

  2. 使用shell脚本定时重启tomcat服务

    #!/bin/bash DATE=`date +%Y-%m-%d-%H-%M-%S` echo "当前时间是:$DATE" # 根据端口号查找进程 PID=`/usr/sbin/l ...

  3. 原生js如果将string类型的数进行值

    原生的tring类型比较会进行隐式转换,如'100'>90 为true

  4. 手把手教你玩转 Gitea|使用 Docker 安装 Gitea

    使用 Docker 安装 Gitea 的过程非常简单的,堪比"一键式"安装.Gitea 安装使用系列教程将会从多种方式进行全方位的实操演示. 视频演示中使用腾讯云实验环境安装 Do ...

  5. springboot自动配置原理以及手动实现配置类

    springboot自动配置原理以及手动实现配置类 1.原理 spring有一个思想是"约定大于配置". 配置类自动配置可以帮助开发人员更加专注于业务逻辑开发,springboot ...

  6. OnionArch - 采用DDD+CQRS+.Net 7.0实现的洋葱架构

    博主最近失业在家,找工作之余,看了一些关于洋葱(整洁)架构的资料和项目,有感而发,自己动手写了个洋葱架构解决方案,起名叫OnionArch.基于最新的.Net 7.0 RC1, 数据库采用Postgr ...

  7. Java 求解自幂数(水仙花数)

    什么是自幂数 如果在一个固定的进制中,一个 n 位自然数等于自身各个数位上数字的 n 次幂之和,则称此数为自幂数. 例如:在十进制中,153 是一个三位数,各个数位的3次幂之和为 1^3+5^3+3^ ...

  8. 频道插件如何对接圈子 齐博x1齐博x2齐博x3齐博x4齐博x5齐博x6齐博x7齐博x8齐博x9齐博x10

    圈子黄页里要显示对应频道的数据列表,一般没有特殊要求的话,不需要建立PHP文件, 只须要做好模板即可,比如 \template\index_style\default\qun\shop\index.h ...

  9. 微服务 Zipkin 链路追踪原理(图文详解)

    一个看起来很简单的应用,可能需要数十或数百个服务来支撑,一个请求就要多次服务调用. 当请求变慢.或者不能使用时,我们是不知道是哪个后台服务引起的. 这时,我们使用 Zipkin 就能解决这个问题. 由 ...

  10. Spring Retry 重试

    重试的使用场景比较多,比如调用远程服务时,由于网络或者服务端响应慢导致调用超时,此时可以多重试几次.用定时任务也可以实现重试的效果,但比较麻烦,用Spring Retry的话一个注解搞定所有.话不多说 ...