最近在项目中遇到了这样一个问题:前后端分离,前端用Vue来做,所有的数据请求都使用vue-resource,没有使用表单,因此数据交互都是使用JSON,后台使用Spring Boot,权限验证使用了Spring Security,因为之前用Spring Security都是处理页面的,这次单纯处理Ajax请求,因此记录下遇到的一些问题。这里的解决方案不仅适用于Ajax请求,也可以解决移动端请求验证。

创建工程

首先我们需要创建一个Spring Boot工程,创建时需要引入Web、Spring Security、MySQL和MyBatis(数据库框架其实随意,我这里使用MyBatis),创建好之后,依赖文件如下:

<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.1</version>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope>
</dependency>
<dependency><groupId>commons-codec</groupId><artifactId>commons-codec</artifactId><version>1.11</version>
</dependency>

注意最后一个commons-codec依赖是我手动加入进来的,这是一个Apache的开源项目,可以用来生成MD5消息摘要,我在后文中将对密码进行简单的处理。

创建数据库并配置

为了简化逻辑,我这里创建了三个表,分别是用户表、角色表、用户角色关联表,如下:

接下来我们需要在application.properties中对自己的数据库进行简单的配置,这里各位小伙伴视自己的具体情况而定。

spring.datasource.url=jdbc:mysql:///vueblog
spring.datasource.username=root
spring.datasource.password=123

构造实体类

这里主要是指构造用户类,这里的用户类比较特殊,必须实现UserDetails接口,如下:

public class User implements UserDetails {private Long id;private String username;private String password;private String nickname;private boolean enabled;private List<Role> roles;@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return enabled;}@Overridepublic List<GrantedAuthority> getAuthorities() {List<GrantedAuthority> authorities = new ArrayList<>();for (Role role : roles) {authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()));}return authorities;}//getter/setter省略...
}

实现了UserDetails接口之后,该接口中有几个方法需要我们实现,四个返回Boolean的方法都是见名知意,enabled表示档期账户是否启用,这个我数据库中确实有该字段,因此根据查询结果返回,其他的为了简单期间都直接返回true,getAuthorities方法返回当前用户的角色信息,用户的角色其实就是roles中的数据,将roles中的数据转换为List<GrantedAuthority>之后返回即可,这里有一个要注意的地方,由于我在数据库中存储的角色名都是诸如‘超级管理员’、‘普通用户’之类的,并不是以ROLE_这样的字符开始的,因此需要在这里手动加上ROLE_,切记

另外还有一个Role实体类,比较简单,按照数据库的字段创建即可,这里不再赘述。

创建UserService

这里的UserService也比较特殊,需要实现UserDetailsService接口,如下:

@Service
public class UserService implements UserDetailsService {@AutowiredUserMapper userMapper;@AutowiredRolesMapper rolesMapper;@Overridepublic UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {User user = userMapper.loadUserByUsername(s);if (user == null) {//避免返回null,这里返回一个不含有任何值的User对象,在后期的密码比对过程中一样会验证失败return new User();}//查询用户的角色信息,并返回存入user中List<Role> roles = rolesMapper.getRolesByUid(user.getId());user.setRoles(roles);return user;}
}

实现了UserDetailsService接口之后,我们需要实现该接口中的loadUserByUsername方法,即根据用户名查询用户。这里注入了两个MyBatis中的Mapper,UserMapper用来查询用户,RolesMapper用来查询角色。在loadUserByUsername方法中,首先根据传入的参数(参数就是用户登录时输入的用户名)去查询用户,如果查到的用户为null,可以直接抛一个UsernameNotFoundException异常,但是我为了处理方便,返回了一个没有任何值的User对象,这样在后面的密码比对过程中一样会发现登录失败的(这里大家根据自己的业务需求调整即可),如果查到的用户不为null,此时我们根据查到的用户id再去查询该用户的角色,并将查询结果放入到user对象中,这个查询结果将在user对象的getAuthorities方法中用上。

Security配置

我们先来看一下我的Security配置,然后我再来一一解释:

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@AutowiredUserService userService;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userService).passwordEncoder(new PasswordEncoder() {@Overridepublic String encode(CharSequence charSequence) {return DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());}/*** @param charSequence 明文* @param s 密文* @return*/@Overridepublic boolean matches(CharSequence charSequence, String s) {return s.equals(DigestUtils.md5DigestAsHex(charSequence.toString().getBytes()));}});}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/admin/**").hasRole("超级管理员").anyRequest().authenticated()//其他的路径都是登录后即可访问.and().formLogin().loginPage("/login_page").successHandler(new AuthenticationSuccessHandler() {@Overridepublic void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {httpServletResponse.setContentType("application/json;charset=utf-8");PrintWriter out = httpServletResponse.getWriter();out.write("{\"status\":\"ok\",\"msg\":\"登录成功\"}");out.flush();out.close();}}).failureHandler(new AuthenticationFailureHandler() {@Overridepublic void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {httpServletResponse.setContentType("application/json;charset=utf-8");PrintWriter out = httpServletResponse.getWriter();out.write("{\"status\":\"error\",\"msg\":\"登录失败\"}");out.flush();out.close();}}).loginProcessingUrl("/login").usernameParameter("username").passwordParameter("password").permitAll().and().logout().permitAll().and().csrf().disable();}@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/reg");}
}

这是我们配置的核心,小伙伴们听我一一道来:

1.首先这是一个配置类,因此记得加上@Configuration注解,又因为这是Spring Security的配置,因此记得继承WebSecurityConfigurerAdapter。
2.将刚刚创建好的UserService注入进来,一会我们要用。
3.configure(AuthenticationManagerBuilder auth)方法中用来配置我们的认证方式,在auth.userDetailsService()方法中传入userService,这样userService中的loadUserByUsername方法在用户登录时将会被自动调用。后面的passwordEncoder是可选项,可写可不写,因为我是将用户的明文密码生成了MD5消息摘要后存入数据库的,因此在登录时也需要对明文密码进行处理,所以就加上了passwordEncoder,加上passwordEncoder后,直接new一个PasswordEncoder匿名内部类即可,这里有两个方法要实现,看名字就知道方法的含义,第一个方法encode显然是对明文进行加密,这里我使用了MD5消息摘要,具体的实现方法是由commons-codec依赖提供的;第二个方法matches是密码的比对,两个参数,第一个参数是明文密码,第二个是密文,这里只需要对明文加密后和密文比较即可(小伙伴如果对此感兴趣可以继续考虑密码加盐)。
4.configure(HttpSecurity http)用来配置我们的认证规则等,authorizeRequests方法表示开启了认证规则配置,antMatchers("/admin/**").hasRole("超级管理员")表示/admin/**的路径需要有‘超级管理员’角色的用户才能访问,我在网上看到小伙伴对hasRole方法中要不要加ROLE_前缀有疑问,这里是不要加的,如果用hasAuthority方法才需要加。anyRequest().authenticated()表示其他所有路径都是需要认证/登录后才能访问。接下来我们配置了登录页面为login_page,登录处理路径为/login,登录用户名为username,密码为password,并配置了这些路径都可以直接访问,注销登陆也可以直接访问,最后关闭csrf。在successHandler中,使用response返回登录成功的json即可,切记不可以使用defaultSuccessUrl,defaultSuccessUrl是只登录成功后重定向的页面,使用failureHandler也是由于相同的原因。
5.configure(WebSecurity web)方法中我配置了一些过滤规则,不赘述。
6.另外,对于静态文件,如/images/**/css/**/js/**这些路径,这里默认都是不拦截的。

Controller

最后来看看我们的Controller,如下:

@RestController
public class LoginRegController {/*** 如果自动跳转到这个页面,说明用户未登录,返回相应的提示即可* <p>* 如果要支持表单登录,可以在这个方法中判断请求的类型,进而决定返回JSON还是HTML页面** @return*/@RequestMapping("/login_page")public RespBean loginPage() {return new RespBean("error", "尚未登录,请登录!");}
}

这个Controller整体来说还是比较简单的,RespBean一个响应bean,返回一段简单的json,不赘述,这里需要小伙伴注意的是login_page,我们配置的登录页面是一个login_page,但实际上login_page并不是一个页面,而是返回一段JSON,这是因为当我未登录就去访问其他页面时Spring Security会自动跳转到到login_page页面,但是在Ajax请求中,不需要这种跳转,我要的只是是否登录的提示,所以这里返回json即可。

测试

最后小伙伴可以使用POSTMAN或者RESTClient等工具来测试登录和权限问题,我就不演示了。

Ok,经过上文的介绍,想必小伙伴们对Spring Boot+Spring Security处理Ajax登录请求已经有所了解了,好了,本文就说到这里,有问题欢迎留言讨论。

更多资料请关注公众号:

SpringBoot+SpringSecurity处理Ajax登录请求相关推荐

  1. Springboot跨域 ajax jsonp请求

    SpringBoot配置: <dependency><groupId>org.springframework.boot</groupId><artifactI ...

  2. springboot+mybatis整合shiro——登录认证和权限控制

    引依赖 shiro-all包含shiro所有的包.shiro-core是核心包.shiro-web是与web整合.shiro-spring是与spring整合.shiro-ehcache是与EHCac ...

  3. SpringSecurity整合springBoot、redis——实现登录互踢

    背景 基于我的文章--<SpringSecurity整合springBoot.redis token动态url权限校验>.要实现的功能是要实现一个用户不可以同时在两台设备上登录,有两种思路 ...

  4. SpringBoot + SpringSecurity 短信验证码登录功能实现

    实现原理 在之前的文章中,我们介绍了普通的帐号密码登录的方式:SpringBoot + Spring Security 基本使用及个性化登录配置(http://www.deiniu.com/artic ...

  5. python 全栈开发,Day87(ajax登录示例,CSRF跨站请求伪造,Django的中间件,自定义分页)...

    python 全栈开发,Day87(ajax登录示例,CSRF跨站请求伪造,Django的中间件,自定义分页) 一.ajax登录示例 新建项目login_ajax 修改urls.py,增加路径 fro ...

  6. SpringBoot 整合JWT实现基于自定义注解的-登录请求验证拦截(保姆级教学,附:源码)

    学习目标: Spring Boot 整合JWT实现基于自定义注解的 登录请求接口拦截 例: 一篇掌握 JWT 入门知识  1.1 在学习SpringBoot 整合JWT之前,我们先来说说JWT进行用户 ...

  7. react axios和ajax网络请求拦截(session过期跳转登录页)

    网络请求拦截经常也可用到,比如在所有接口消息请求头部加上验证信息token之类的,或者接口报某类错误时统一处理.这里主要用在请求接口时,判断session是否过期,统一返回状态码区分,如果过期了跳转登 ...

  8. 如何用ajax做登录页面,ajax如何制作登录页面?登录页面ajax的请求详解(附完整实例)...

    本篇文章主要的讲述了关于ajax登录页面时ajax请求的内容,现在我们一起来看看这篇文章吧 登录页面ajax请求 一.登录验证提示信息//提交登录信息sub.on('click',function(e ...

  9. Springsecurity搭建自定义登录页面

    Springsecurity搭建自定义登录页面 1.springSecurity的搭建 新建一个springboot的web项目,我这边只选中了web,建立后如下: image.png pom依赖: ...

最新文章

  1. dp背包九讲(待补充,暂时前两讲)
  2. python 易支付sdk
  3. imu与gps之间的时间戳_一个时间戳精度问题,引发了一个MySQL血案
  4. BZOJ-1968: [Ahoi2005]COMMON 约数研究 (思想)
  5. 在linux运行math_neon库,linux - 仅使用带交叉编译器的本地库 - 堆栈内存溢出
  6. Java阻塞队列ArrayBlockingQueue和LinkedBlockingQueue实现原理分析
  7. C语言,利用循环语句找出1000以内的水仙花数
  8. ERROR: Couldn't connect to Docker daemon at http+docker://localunixsocket - is it running?
  9. java 中_l1,L2指令获取错过远高于L1指令获取未命中
  10. 【中秋快乐】求问meta-learning和few-shot learning的关系是什么?
  11. 【ts】有关报错Line 0: Parsing error: Cannot read property ‘map‘ of undefined的解决方法
  12. 任务调度:开源大数据调度框架Taier(太阿)
  13. 部分插件由于缺少依赖无法加载。要恢复这些插件提供的功能,需要修复这些问题并重启 Jenkins的解决办法
  14. 学习批处理之安装一键装机必备软件
  15. hive LZO压缩
  16. 互联网化企业软件应该具备什么功能
  17. java动态编程解决分硬币问题,动态编程硬币更改问题
  18. html列表ppt,HTML教程列表与表格.ppt
  19. mysql导入数据表
  20. Windows 10 MSDN官方原版ISO镜像(简体中文)下载

热门文章

  1. python有多少关键字_Python中有几个关键字
  2. 1-动态图形程序-略污
  3. 收集整理1-2个自己熟知的假设检验问题,并给出检验统计量的分布。
  4. DTSE Tech Talk丨第3期:解密数据隔离方案,让SaaS应用开发更轻松
  5. 2019年数维杯数学建模B题火灾等级评价与快速救援措施优化求解全过程文档及程序
  6. tp5如何在模板格式化显示时间_华为手机如何才能息屏显示时间?手把手教你,一秒就能学会...
  7. SAS期末复习知识点总结(应用多元统计实验笔记)
  8. 无损APE,FLAC,盗版CD与原CD的差别(转载)
  9. linux网络存储备份,备份基础:Linux异构网络共享光盘刻录(下)
  10. linux如何安装网卡驱动