接着上章节下面我们做一下身份认证
登录成功之后前端就可以获取到了jwt的信息,所以后端进行用户身份识别的时候,我们需要通过请求头中获取jwt,然后解析出我们的用户名,这样我们就可以知道是谁在访问我们的接口啦,然后判断用户是否有权限等操作
那么我们自定义一个过滤器用来进行识别jwt
JWTAuthenticationFilter
代码如下
import cn.hutool.core.util.StrUtil;
import com.rao.service.SysUserService;
import com.rao.utils.JwtUtils;
import com.rao.utils.RedisUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.TreeSet;
@Slf4j
public class JWTAuthenticationFilter extends BasicAuthenticationFilter {
@Autowired
JwtUtils jwtUtils;
@Autowired
RedisUtil redisUtil;
@Autowired
SysUserService sysUserService;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
log.info("jwt 校验filter");
String jwt = request.getHeader(jwtUtils.getHeader());
if (StrUtil.isBlankOrUndefined(jwt)){
chain.doFilter(request,response);
return;
}
Claims claimByToken = jwtUtils.getClaimByToken(jwt);
if (claimByToken==null){
throw new JwtException("token异常");
}
if(jwtUtils.isTokenExpired((Claims) claimByToken.getExpiration())){
throw new JwtException("token异常");
}
String username = claimByToken.getSubject();
log.info("用户-{},正在登录!",username);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(username,null,new TreeSet<>());
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
chain.doFilter(request,response);
}
}
上面的逻辑也很简单,正如我前面说到的,获取到用户名之后我们直接把封装成UsernamePasswordAuthenticationToken,之后交给SecurityContextHolder参数传递authentication对象,这样后续security就能获取到当前登录的用户信息了,也就完成了用户认证。
当认证失败的时候会进入AuthenticationEntryPoint,于是我们自定义认证失败返回的数据
com.markerhub.security.JwtAuthenticationEntryPoint
import cn.hutool.json.JSONUtil;
import com.rao.common.lang.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@Slf4j
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
log.info("认证失败!未登录");
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
ServletOutputStream outputStream = response.getOutputStream();
Result fail = Result.fail("请先登录!");
outputStream.write(JSONUtil.toJsonStr(fail).getBytes(StandardCharsets.UTF_8));
outputStream.flush();
outputStream.close();
}
}
不过是啥原因,认证失败,我们就要求重新登录,所以返回的信息直接明了“请先登录!”哈哈。
然后我们把认证过滤器和认证失败入口配置到SecurityConfig中:
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.formLogin()
.failureHandler(loginFailureHandler)
.successHandler(loginSuccessHandler)
.and()
.authorizeRequests()
.antMatchers(URL_WHITELIST).permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
//不会创建session
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilter(jwtAuthenticationFilter())
.addFilterBefore(captchaFilter, UsernamePasswordAuthenticationFilter.class)
;
}
@Bean
JWTAuthenticationFilter jwtAuthenticationFilter()throws Exception{
JWTAuthenticationFilter jwtAuthenticationFilter = new JWTAuthenticationFilter(authenticationManager());
return jwtAuthenticationFilter;
}
身份认证:
之前我们的用户名密码配置在配置文件中的,而且密码也用的是明文,这明显不符合我们的要求,我们的用户必须是存储在数据库中,密码也是得经过加密的。所以我们先来解决这个问题,然后再去弄授权。
下面我们接着讲如何将密码进行加密的方式
com.markerhub.config.SecurityConfig
@Bean
BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
在此基础加上测试路径加在白名单上:
测试一下
@Autowired
SysUserService userService;
@Autowired
BCryptPasswordEncoder bCryptPasswordEncoder;
@GetMapping("/test/pass")
public Result test(){
// 密码加密
String pass = bCryptPasswordEncoder.encode("111111");
// 密码验证
boolean matches = bCryptPasswordEncoder.matches("111111", pass);
return Result.succ(MapUtil.builder()
.put("pass", pass)
.put("marches", matches)
.build()
);
}
这样系统就会使用我们找个新的密码策略进行匹配密码是否正常了。之前我们配置文件配置的用户名密码去掉:
- application.yml
# security:
# user:
# name: user
# password: 111111
测试成功密码111111加密完成
把admin的密码换成自己加密的密码
但是先我们登录过程系统不是从我们数据库中获取数据的,因此,我们需要重新定义这个查用户数据的过程,我们需要重写UserDetailsService接口。
com.markerhub.security.UserDetailsServiceImpl
package com.rao.security;
import com.rao.entity.SysUser;
import com.rao.service.SysUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
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.TreeSet;
@Slf4j
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
SysUserService sysUserService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser user = sysUserService.getByUsername(username);
if (user==null){
throw new UsernameNotFoundException("用户名或密码不正确!");
}
return new AccountUser(user.getId(), user.getUsername(), user.getPassword(), new TreeSet<>());
}
}
因为security在认证用户身份的时候会调用UserDetailsService.loadUserByUsername()方法,因此我们重写了之后security就可以根据我们的流程去查库获取用户了。然后我们把UserDetailsServiceImpl配置到SecurityConfig中:
- com.markerhub.config.SecurityConfig
@Autowired
UserDetailsServiceImpl userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
然后上面UserDetailsService.loadUserByUsername()默认返回的UserDetails,我们自定义了AccountUser去重写了UserDetails,这也是为了后面我们可能会调整用户的一些数据等。
com.markerhub.security.AccountUser
package com.rao.security;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.Assert;
import java.util.Collection;
public class AccountUser implements UserDetails {
private Long userId;
private String password;
private final String username;
private final Collection<? extends GrantedAuthority> authorities;
private final boolean accountNonExpired;
private final boolean accountNonLocked;
private final boolean credentialsNonExpired;
private final boolean enabled;
public AccountUser(Long userId, String username, String password, Collection<? extends GrantedAuthority> authorities) {
this(userId, username, password, true, true, true, true, authorities);
}
public AccountUser(Long userId, String username, String password, boolean enabled, boolean accountNonExpired,
boolean credentialsNonExpired, boolean accountNonLocked,
Collection<? extends GrantedAuthority> authorities) {
Assert.isTrue(username != null && !"".equals(username) && password != null,
"Cannot pass null or empty values to constructor");
this.userId = userId;
this.username = username;
this.password = password;
this.enabled = enabled;
this.accountNonExpired = accountNonExpired;
this.credentialsNonExpired = credentialsNonExpired;
this.accountNonLocked = accountNonLocked;
this.authorities = authorities;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.username;
}
@Override
public boolean isAccountNonExpired() {
return this.accountNonExpired;
}
@Override
public boolean isAccountNonLocked() {
return this.accountNonLocked;
}
@Override
public boolean isCredentialsNonExpired() {
return this.credentialsNonExpired;
}
@Override
public boolean isEnabled() {
return this.enabled;
}
}
测试一下获取验证码
然后登录
测试成功