本文是V部落项目在完成过程中的副产品,完整项目在这里:
https://github.com/lenve/VBlog最近在项目中遇到了这样一个问题:前后端分离,前端用Vue来做,所有的数据请求都使用vue-resource,没有使用表单,因此数据交互都是使用JSON,后台使用Spring Boot,权限验证使用了Spring Security,因为之前用Spring Security都是处理页面的,这次单纯处理Ajax请求,因此记录下遇到的一些问题。这里的解决方案不仅适用于Ajax请求,也可以解决移动端请求验证。
创建工程首先我们需要创建一个Spring Boot工程,创建时需要引入Web、Spring Security、MySQL和MyBatis(数据库框架其实随意,我这里使用MyBatis),创建好之后,依赖文件如下:
org.mybatis.spring.boot mybatis-spring-boot-starter1.3.1 org.springframework.boot spring-boot-starter-securityorg.springframework.boot spring-boot-starter-webmysql mysql-connector-javaruntime commons-codec commons-codec1.11
注意最后一个commons-codec依赖是我手动加入进来的,这是一个Apache的开源项目,可以用来生成MD5消息摘要,我在后文中将对密码进行简单的处理。
创建数据库并配置为了简化逻辑,我这里创建了三个表,分别是用户表、角色表、用户角色关联表,如下:
![p271]()
接下来我们需要在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 roles;
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return enabled;
}
@Override
public List getAuthorities() {
List 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
另外还有一个Role实体类,比较简单,按照数据库的字段创建即可,这里不再赘述。
创建UserService这里的UserService也比较特殊,需要实现UserDetailsService接口,如下:
@Service
public class UserService implements UserDetailsService {
@Autowired
UserMapper userMapper;
@Autowired
RolesMapper rolesMapper;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
User user = userMapper.loadUserByUsername(s);
if (user == null) {
//避免返回null,这里返回一个不含有任何值的User对象,在后期的密码比对过程中一样会验证失败
return new User();
}
//查询用户的角色信息,并返回存入user中
List 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 {
@Autowired
UserService userService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(new PasswordEncoder() {
@Override
public String encode(CharSequence charSequence) {
return DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
}
@Override
public boolean matches(CharSequence charSequence, String s) {
return s.equals(DigestUtils.md5DigestAsHex(charSequence.toString().getBytes()));
}
});
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin
@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登录请求已经有所了解了,好了,本文就说到这里,有问题欢迎留言讨论。



