博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
基于Spring Security前后端分离式项目解决方案
阅读量:4045 次
发布时间:2019-05-24

本文共 8981 字,大约阅读时间需要 29 分钟。

基于Spring Security前后端分离式项目解决方案

Spring Security 简介

Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架,是保护基于spring应用的实际标准。

Spring Security专注于为Java应用提供认证和授权,并且可以轻松扩展以满足自定义要求。

  • 认证:访问者的身份验证,如登录
  • 授权:访问者的权限控制,即可以干什么,不可以干什么

Spring Security的核心就是一组可配置的过滤器,请求访问资源之前被这些过滤器层层拦截,按照配置每一个过滤器都对请求执行自身的验证逻辑,通过则到下一个过滤器,如果一直通过,最终到达访问资源,其中任何一个未通过,则被拦截无法到达访问资源。

Spring Securit 直接提供了登录和退出功能,并提供了当前用户信息的模板。

Spring Security的应用方案

技术框架

后端

Spring Boot 、Spring Security 、MyBatis

前端

vue-cli 、vue、axios、elementui

基本思路

后端

  1. 支持跨域
  2. 禁用CSRF(如果不禁用CSRF,session不易跟踪)
  3. 密码在数据库中加密存储
  4. 认证成功或失败后、未认证被拦截后、未授权被拦截后 和 退出系统后一律向前端发送如下示例格式json串的结果数据。
    {
    "authorized":true,//是否已授权 "logined":true,//是否已登录 "message":"信息",//信息 "success":true //是否成功}

前端

  1. 自定义登录页,不使用Spring Security提供的登录页
  2. 前端对处理代码进行封装,配合处理后端返回认证授权验证结果数据

后端具体实现

引入Spring Security依赖

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

建立安全配置类

在web应用中,Spring Security配置类需要继承WebSecurityConfigurerAdapter,重写其中的配置方法,主要的核心配置都在配置方法中。

  • 配置类的总体结构

    在这里插入图片描述

  • 配置密码编码器具体代码

    //配置密码编码器    @Bean    public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder(); }

    BCryptPasswordEncoder是Spring Security提供的使用哈希算法结合盐值(盐值即一个安全随机数)加密器,该类对同一明文每次加密都不一样,哈希又是一种不可逆算法,所以密码认证时需要使用相同的方式对待校验的明文进行加密,然后比较这两个密文来进行验证。

  • 注入安全Dao的具体代码

    //安全Dao负责从数据库中获取认证和授权等数据    private final SecurityDao securityDao;    //构造方法    public SecurityConfig(SecurityDao securityDao) {
    this.securityDao = securityDao; }

    SecurityDao并非Spring Security提供的,是自定义的访问数据库的对象。

  • 配置UserDetailsService对象的具体代码

    UserDetailsService接口由Spring Security 提供,其作用为根据用户名(账号)加载当前用户信息,定义如下:

    public interface UserDetailsService{
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException}

    当前用户信息UserDetials也是Spring Security 提供的接口 ,定义如下:

    public  interface UserDetails{
    public Collection
    getAuthorities(); //获取当前用户的权限 public String getPassword(); //获取当前用户的密码,此处获取的密码应当加密形式 public String getUsername(); //获取当前用户名(账号) public boolean isAccountNonExpired(); //当前用户是否未过期 public boolean isAccountNonLocked(); //当前用户是否未锁定 public boolean isCredentialsNonExpired(); //当前用户凭证(密码)是否未过期 public boolean isEnabled(); //当前用户是否可用}

    配置UserDetailsService对象的作用有两个,一个是提供自定义UserDetailsService的实现并作为spring的bean对象,另一个是提供UserDetails 实现,具体代码如下:

    //配置UserDetailsService对象,用于用户获取当前用户信息    @Bean    public UserDetailsService userDetailsService(SecurityDao securityDao) {
    /* 匿名实现UserDetailsService接口, 该接口中仅有一个方法public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException, 此方法的功能是依据登录用户名(可以理解为账号)加载当前用户信息,UserDetials接口表示当前用户信息 */ return userId -> {
    User user = securityDao.findUserByUserId(userId);//根据账号从数据库中查询用户信息 if (user == null) throw new UsernameNotFoundException("账号不正确!"); //根据账号从数据库中权限编号的集合 List
    moduleIdList = securityDao.findModuleIdListByUserId(userId); List
    authorityList = new ArrayList<>();//权限集合 String authorityPattern = "ROLE_{0}";//在Spring Security中,权限的名称格式为“ROLE_XXX” //将权限编号转换为“ROLE_权限编号”的形式,然后封装为SimpleGrantedAuthority对象放入集合中 for (String moduleId : moduleIdList) {
    authorityList.add(new SimpleGrantedAuthority(MessageFormat.format(authorityPattern, moduleId))); } //CurrUser是一个自定义的类实现了UserDetails接口 return new CurrUser( user.getU_id(), user.getU_name(), user.getU_pwd(), user.getU_status().equals(DataStatusEnum.已启用.getCode()), authorityList ); }; }

    上述配置中的CurrUser是自定义的UserDetails实现,具体代码如下:

    public class CurrUser implements UserDetails {
    private String username;//账号 private String factname;//姓名 private String password; private boolean enabled; private Collection
    authorities; public CurrUser() {
    } public CurrUser(String username, String factname, String password, boolean enabled, Collection
    authorities) {
    this.username = username; this.factname = factname; this.password = password; this.enabled = enabled; this.authorities = authorities; } @Override public Collection
    getAuthorities() {
    return authorities; } @Override public String getPassword() {
    return password; } @Override public String getUsername() {
    return username; } public String getFactname(){
    return factname; } @Override public boolean isAccountNonExpired() {
    return true; } @Override public boolean isAccountNonLocked() {
    return true; } @Override public boolean isCredentialsNonExpired() {
    return true; } @Override public boolean isEnabled() {
    return enabled; }}
  • 核心安全配置

    重写config(HttpSecurity)方法,其中写核心的安全配置,配置内容概括如下:
    (1)通过查询数据库,将模块地址和相应权限编号对应起来,访问某个模块必须要有相应的权限;
    (2)其它地址请求需要认证;
    (3)配置登录,包括登录处理地址、登录账号密码参数名称、登录成功/失败的响应,以及允许所有请求访问登录相关地址(接口)等;
    (4)配置退出,包括退出地址 和 退出成功的响应;
    (5)配置异常处理,包括未认证异常和未授权异常
    (6)配置允许跨域
    (7)配置禁用防CSRF攻击

    具体配置代码如下:

    //重写WebSecurityConfigurerAdapter的configure(HttpSeeurity)方法,核心的配置都在此方法中    @Override    protected void configure(HttpSecurity http) throws Exception {
    //证授权配置-开始 String antUrlPattern = "{0}/**";//地址模式 List
    moduleList = securityDao.findModuleList();//获得所有权限 ExpressionUrlAuthorizationConfigurer
    .ExpressionInterceptUrlRegistry authorize = http.authorizeRequests(); for (Module module : moduleList) {
    //为每一个地址模式匹配一个权限(角色) authorize .antMatchers(MessageFormat.format(antUrlPattern, module.getM_url())) .hasRole(module.getM_id().toString()); } authorize .anyRequest().authenticated()//其它请求需要认证 .and()//此方法返回ExpressionUrlAuthorizationConfigurer
    .ExpressionInterceptUrlRegistry对象 //以下登录配置开始 .formLogin() .loginProcessingUrl("/login")//登录处理地址 .usernameParameter("u_id")//定义登录时,用户名的参数名,默认为 username .passwordParameter("u_pwd")//定义登录时,密码的参数名,默认为 password .successHandler((req, resp, authentication) -> {
    resp.setContentType("application/json;charset=utf-8"); PrintWriter out = resp.getWriter(); out.write(JSON.toJSONString(Result.success("登录成功"))); out.flush(); })//登录成功的处理器 .failureHandler((req, resp, exception) -> {
    resp.setContentType("application/json;charset=utf-8"); PrintWriter out = resp.getWriter(); out.write(JSON.toJSONString(Result.fail("登录失败!"))); out.flush(); })//登录失败的处理器 .permitAll();//登录相关访问地址一律放行 //以上登录配置 //认证授权配置-结束 http //以下退出配置 .logout() .logoutUrl("/logout")//退出地址 .logoutSuccessHandler((req, resp, authentication) -> {
    resp.setContentType("application/json;charset=utf-8"); PrintWriter out = resp.getWriter(); out.write(JSON.toJSONString(Result.success("您已成功退出系统!"))); out.flush(); })//退出成功的处理器 .permitAll()//退出地址一律放行 //以上退出配置 .and() //返回HttpSecurity对象 //以下异常处理配置 .exceptionHandling() //未认证用户访问需要认证资源异常处理 .authenticationEntryPoint((httpServletRequest, httpServletResponse, e) -> {
    httpServletResponse.setContentType("application;charset=UTF-8"); PrintWriter out = httpServletResponse.getWriter(); out.print(JSON.toJSONString(Result.unlogined())); out.flush(); }) //认证用户访问资源权限不足异常处理 .accessDeniedHandler((httpServletRequest, httpServletResponse, e) -> {
    httpServletResponse.setContentType("application;charset=UTF-8"); PrintWriter out = httpServletResponse.getWriter(); out.print(JSON.toJSONString(Result.unauthorized())); out.flush(); }) //以上异常处理配置 .and() .cors()//允许跨域,如果springboot/springmvc已有跨域配置,自动采用springboot/springmvc跨域配置 .and() //禁用防CSRF攻击 .csrf().disable(); }

当前用户信息的获取

在spring mvc中,可以通过在控制器处理方法上定义Principal类型的参数获得经过认证的当前用户信息,示例如下:

@GetMapping("/curruser")    public CurrUser currUser(Principal principal){
UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken)principal; CurrUser currUser = (CurrUser) token.getPrincipal(); return currUser; }

前端具体实现

  1. 登录页向登录处理地址(该地址已在服务器配置)以form-data方式发送post请求;
  2. 封装js统一处理授权认证响应。

附录

转载地址:http://enhdi.baihongyu.com/

你可能感兴趣的文章
socket编程中select的使用
查看>>
GitHub 万星推荐:黑客成长技术清单
查看>>
可以在线C++编译的工具站点
查看>>
关于无人驾驶的过去、现在以及未来,看这篇文章就够了!
查看>>
所谓的进步和提升,就是完成认知升级
查看>>
为什么读了很多书,却学不到什么东西?
查看>>
长文干货:如何轻松应对工作中最棘手的13种场景?
查看>>
如何用好碎片化时间,让思维更有效率?
查看>>
No.147 - LeetCode1108
查看>>
No.174 - LeetCode1305 - 合并两个搜索树
查看>>
No.175 - LeetCode1306
查看>>
No.176 - LeetCode1309
查看>>
No.182 - LeetCode1325 - C指针的魅力
查看>>
mysql:sql alter database修改数据库字符集
查看>>
mysql:sql truncate (清除表数据)
查看>>
yuv to rgb 转换失败呀。天呀。谁来帮帮我呀。
查看>>
yuv420 format
查看>>
yuv420 还原为RGB图像
查看>>
LED恒流驱动芯片
查看>>
驱动TFT要SDRAM做为显示缓存
查看>>