Spring Security(7)
创始人
2024-03-02 14:58:48
0

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

有时某些业务或者功能,需要在用户请求到来之前就进行一些判断或执行某些动作,就像在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 {@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException {// 保存参数=参数值对Map mapResult = new HashMap();// 请求中的参数Enumeration 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;// 验证signsign = 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 {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;}@Overridepublic 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 {@Autowiredprivate MySQLDao mySQLDao;// 得到某个角色的全部权限public List 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 {@Autowiredprivate RoleService roleService;@Autowiredprivate PermissionService permissionService;@Overridepublic boolean hasPermission(Authentication authentication, Object targetUrl, Object permission) {// 获得loadUserByUsername()方法的结果User user = (User) authentication.getPrincipal();// 获得用户授权Collection authorities = user.getAuthorities();// 遍历用户所有角色for(GrantedAuthority authority : authorities) {String roleName = authority.getAuthority();int roleid = roleService.getByName(roleName).getId();// 得到角色所有的权限List permissionList = permissionService.getByRoleId(roleid);if (null == permissionList) {continue;}// 遍历permissionListfor(SysPermission sysPermission : permissionList) {String pstr = sysPermission.getPermission();String path = sysPermission.getPath();// 判空if (StringUtils.isBlank(pstr) || StringUtils.isBlank(path)) {continue;}// 如果访问的url和权限相符,返回trueif (path.equals(targetUrl) && pstr.equals(permission)) {return true;}}}return false;}@Overridepublic 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

从结果可以看到:

 


感谢您的大驾光临!咨询技术、产品、运营和管理相关问题,请关注后留言。欢迎骚扰,不胜荣幸~

相关内容

热门资讯

AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWR报告解读 WORKLOAD REPOSITORY PDB report (PDB snapshots) AW...
AWS管理控制台菜单和权限 要在AWS管理控制台中创建菜单和权限,您可以使用AWS Identity and Access Ma...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
群晖外网访问终极解决方法:IP... 写在前面的话 受够了群晖的quickconnet的小水管了,急需一个新的解决方法&#x...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
Azure构建流程(Power... 这可能是由于配置错误导致的问题。请检查构建流程任务中的“发布构建制品”步骤,确保正确配置了“Arti...