配置Spring Security
在开始配置Spring Security之前,有必要大致了解一下它的登录流程做个。

Spring Security进行身份验证的是
AuthenticationManager
接口。ProviderManager
是AuthenticationManager
的一个默认实现。ProviderManager
把验证工作委托给了AuthenticationProvider
接口。AuthenticationProvider
的实现类DaoAuthenticationProvider
会检查身份认证。DaoAuthenticationProvider
又把认证工作委托给了UserDetailsService
接口。自定义
UserDetailsService
类从数据库中获取用户账号、密码、角色等信息,然后封装成UserDetails
返回。使用Spring Security还需要自定义
AuthenticationProvider
接口,获取用户输入的账号、密码等信息,并封装成Authentication
接口。将
UserDetails
和Authentication
进行比对,如果一致就返回UsernamePasswordAuthenticationToken
,否则抛出异常。
所以需要先自定义UserDetailsService
接口。
package com.xiangwang.vmall.security;
import com.xiangwang.vmall.entity.SysRole;
import com.xiangwang.vmall.entity.SysUser;
import com.xiangwang.vmall.entity.SysUserRole;
import com.xiangwang.vmall.service.RoleService;
import com.xiangwang.vmall.service.UserRoleService;
import com.xiangwang.vmall.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
/**
* 自定义用户详情服务接口
*
*/
@Service("userDetailsService")
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserService userService;
@Autowired
private RoleService roleService;
@Autowired
private UserRoleService userRoleService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Collection<GrantedAuthority> authorities = new ArrayList<>();
// 从数据库中取出用户信息
SysUser user = userService.getByName(username);
// 判断用户是否存在
if(null == user) {
System.out.println("user is not exist");
throw new UsernameNotFoundException("user is not exist");
}
// 获得用户角色:方式一
List<SysUserRole> list = userRoleService.getByUserId(user.getId());
// // 获得用户角色:方式二
// List<SysRole> list = roleService.getByUserId(user.getId());
// // 给用户添加授权:方式一
// for (SysUserRole userRole : list) {
// SysRole role = roleService.getById(userRole.getRoleid());
// authorities.add(new SimpleGrantedAuthority(role.getName()));
// }
// // 返回UserDetails实现类
// return new User(user.getName(), user.getPassword(), authorities);
// 给用户添加授权:方式二
return User
.withUsername(username)
.password(user.getPassword())
.authorities(list.stream()
.filter(Objects::nonNull)// 判断是否为空
.map(userRole -> roleService.getById(userRole.getRoleid()))// 从SysUserRole获取Role
.map(SysRole::getName)// 转变为角色名称字符串
.map(SimpleGrantedAuthority::new)// 依据角色名称创建SimpleGrantedAuthority
.toArray(SimpleGrantedAuthority[]::new)// list转变为数组
).build();
}
}
UserDetailsService
返回了封装的UserDetails
,还需要再自定义AuthenticationProvider
返回Authentication
接口和它比对。
package com.xiangwang.vmall.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import java.util.Collection;
/**
* 自定义登录验证
*
*/
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Autowired
private CustomUserDetailsService customUserDetailsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// 获取表单输入中返回的用户名
String username = (String) authentication.getPrincipal();
// 获取表单中输入的密码
String password = (String) authentication.getCredentials();
// 这里调用我们的自己写的获取用户的方法
UserDetails userInfo = customUserDetailsService.loadUserByUsername(username);
if (userInfo == null) {
System.out.println("user is not exist");
throw new UsernameNotFoundException("user is not exist");
}
PasswordEncoder passwordEncoder = new PasswordEncoder() {
@Override
public String encode(CharSequence charSequence) {
return charSequence.toString();
}
@Override
public boolean matches(CharSequence charSequence, String s) {
return s.equals(charSequence.toString());
}
};
// 采用简单密码验证
if (!passwordEncoder.matches(password, userInfo.getPassword())) {
System.out.println("user or password error");
throw new BadCredentialsException("user or password error");
}
Collection<? extends GrantedAuthority> authorities = userInfo.getAuthorities();
// 构建返回的用户登录成功的token
return new UsernamePasswordAuthenticationToken(userInfo, password, authorities);
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
最后,通过重写WebSecurityConfigurerAdapter
中的相关方法来自定义Spring Security的配置。
WebSecurityConfigurerAdapter
主要做下面这几件事。
初始化
:通过init()
方法实现。开启Security
:getHttp()
。配置各种过滤器
:重写configure()
方法,定义FilterChain
和各种Filter
。
package com.xiangwang.vmall.configuration;
import com.xiangwang.vmall.security.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* spring security验证配置
*
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private CustomUserDetailsService customUserDetailsService;
@Autowired
private CustomAuthenticationProvider authenticationProvider;
// 自定义的登录验证逻辑
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
// 控制逻辑
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
// 如果允许有匿名的url通过,可以这样写,例如,注册验证码接口就不需要权限
.antMatchers("/verifyCode").permitAll()
// 设置登录页
.and().formLogin().loginPage("/login")
// 跨域访问
.and().cors();
// 关闭CSRF跨域
http.csrf().disable();
}
@Override
public void configure(WebSecurity web) throws Exception {
// 设置拦截忽略文件夹,可以对静态资源放行
web.ignoring().antMatchers("/css/**", "/js/**");
}
}
重启服务,然后再次用Postman访问接口

可以看到,login
接口访问成功了。
但除了login
接口以外,其他都无法访问。
稍稍总结,以上过程的调用时序图如下。

感谢支持
更多内容,请移步《超级个体》。