整体思路
- 前端页面登录,绑定对象,发送axious到后端,进行验证
- 后端接收到前端的信息,此时密码为明文,而数据库中密码为密文,只能通过用户名查询数据库,判断用户是否存在,如果存在,通过BCryptPasswordEncoder对密码进行验证
- 验证密码通过后,查询用户的角色和权限信息,放到用户对象中,此时只需要字符串即可
- 通过私钥进行加密,颁发令牌到前端
- 前端接收到登录后的响应,判断是否成功登录,如果成功将token存在localstorage中,然后通过路由跳转页面到首页,登录操作完毕
- 此时访问其他资源会报403,因为springsecurity的上下文中并没有用户信息,用户角色和用户的权限,这个工作由springsecurity的FilterSecurityInterceptor这个过滤器来执行,我们需要在basicAuthenticationFilter这个过滤器生效时重写,验证token并将token中的用户信息放到springsecurity的上下文中
- 重写basicAuthenticationFilter时注意,此过滤器在FilterSecurityInterceptor之前,先要放行登录和验证请求,必须要return,使用SecurityContextHolder获取context上下文设置认证信息,这个认证信息需要UsernamepasswordAuthenticationToken对象利用构造方法创建,形参token中的载荷,null,authorities,其中的authorities通过AuthoritUtils中的commaSeparatedStringtoAuthorityList获取
- 前端发送请求时就需要将正确的token放到请求头中,后端验证之后方可访问资源,在vue的utils下的request.js中请求前从localstorage中获取token,然后设置到请求头中
- 此时如若token不正确或过期,无法访问资源,但依然会停留在访问页面,这样的用户体验不好,所以需要增加一个认证环节
- 后台认证:同样利用公钥解析请求头中的token,此过程无异常则表示token合法,正常返回一个ResultVO,需要注意的是:该认证的请求也需要在basicAuthenticationFilter和security配置类中放行
- 前端认证:在main.js中解开之前注释的permission组件,在permission.js中发送后台认证的请求,通过返回的数据判断token是否有效,有效则让原请求去访问资源,需要注意的是:要判断该请求是否是登录请求,若是登录请求可直接放行,访问登录页面,若token不合法就跳回登录页面
1. 登录认证,颁发令牌
- 直接使用vue的index.vue界面,改变其中username和password的绑定
import request from '@/utils/request'
export function login(data) {
return request({
url: '/user/login',
method: 'post',
data
})
}
@PostMapping("login")
@ApiOperation("用户登录")
public ResultVO login(@RequestBody SysUser sysUser) {
return userService.login(sysUser);
}
@Override
public ResultVO login(SysUser loginUser) {
try {
if (loginUser == null) {
return new ResultVO(false, "用户名或密码不正确");
}
// 通过用户名查询用户是否存在,因为密码再数据库中是密文
SysUser userInDB = userMapper.findUserByUserName(loginUser.getUserName());
if (userInDB == null) {
// 用户不存在
return new ResultVO(false, "用户名或密码不正确");
}
// 用户存在,对比密码
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
// 前一个参数是明文密码,后一个是数据库中的密文密码
boolean matches = encoder.matches(loginUser.getPassword(), userInDB.getPassword());
if (!matches) {
// 密码不匹配
return new ResultVO(false, "用户名或密码不正确");
}
// 密码匹配,颁发令牌
// 1.获取用户的角色信息
List rolesList = userMapper.findRolesByUserId(String.valueOf(userInDB.getUserId()));
String roles = "";
// 遍历集合
for (String roleName : rolesList) {
roles += roleName + ",";
}
roles = roles.substring(0, roles.length() - 1);
// 设置用户的角色信息
userInDB.setRoles(roles);
// 2.获取用户的权限信息
List permissionList = userMapper.findPermissionByUserId(userInDB.getUserId());
String permissions = "";
// 遍历集合
for (String permissionName : permissionList) {
permissions += permissionName + ",";
}
permissions = permissions.substring(0, permissions.length() - 1);
// 设置用户的权限信息
userInDB.setPermissions(permissions);
// token载荷中不要放敏感信息
userInDB.setPassword("");
// 3.获取密钥
PrivateKey privateKey = RsaUtils.getPrivateKey(ResourceUtils.getFile("classpath:rsa_pri").getPath());
// 4. 颁发令牌
String token = JwtUtils.generateTokenExpireInMinutes(userInDB, privateKey, 45);
// 5. 没有异常正常返回
return new ResultVO(true, "登录成功", token);
} catch (Exception e) {
e.printStackTrace();
return new ResultVO(false, "令牌不合法!!!");
}
}
// 通过用户名查询用户
@Select("select * from sys_user where user_name = #{userName}")
public SysUser findUserByUserName(String userName);
//查询用户的角色信息
@Select("SELECT role.role_name FROM sys_user_role sur " +
"LEFT JOIN sys_role role ON sur.role_id = role.role_code " +
"WHERe sur.user_id = #{userId}")
public List findRolesByUserId(String userId);
// 查询用户权限信息
@Select("SELECT per.perm_name FROM sys_user_role sur n" +
"LEFT JOIN sys_role_permission srp ON sur.role_id = srp.role_idn" +
"LEFT JOIN sys_permission per ON srp.perm_id = per.perm_coden" +
"WHERe sur.user_id = 1;")
public List findPermissionByUserId(Integer userId);
2. 配置信息
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
// 放行静态资源
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(
"/swagger-ui.html
router.beforeEach(async(to, from, next) => {
// start progress bar
NProgress.start()
// set page title
document.title = getPageTitle(to.meta.title)
verify().then(res => {
if (!res.success) {
if (to.path === '/login') {
next()
} else {
// 跳回登录页面
next(`/login?redirect=${to.path}`)
NProgress.done()
}
} else {
// 放行
next()
}
})
})