为了账号安全,请及时绑定邮箱和手机立即绑定

Spring Security(7)

标签:
JAVA

您好,我是湘王,这是我的慕课手记,欢迎您来,欢迎您再来~



有时某些业务或者功能,需要在用户请求到来之前就进行一些判断或执行某些动作,就像在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已经定义好可以直接使用的过滤器有下面这些

https://img1.sycdn.imooc.com/638607cd0001accf09320644.jpg

 

比如现在的互联网应用都有一个通用的业务规则在执行所有功能接口的时候都要检查确认接口签名的有效性所谓接口签名其实就是一套进入准则客户端按照服务器规定的方式向服务器证明自己确实是某个网站的用户

那么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测试后的效果为

https://img1.sycdn.imooc.com/63860c91000109e908500387.jpg


增加了接口需要的签名参数

 

在前面的内容中几乎没有对Spring Security真正的核心功能也就是认证授权做什么说明也只是简单演示了一些admin角色登录那么在做完前面这些铺垫之后就需要接着来说说这一块了

先创建创建sys_permission表为认证授权的细化做准备

https://img1.sycdn.imooc.com/63860cdd0001cf0b09300333.jpg


同样需要创建实体类和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

从结果可以看到

https://img1.sycdn.imooc.com/63860e2c000171a204870431.jpg

 



 

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



点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消