SpringSecurity整合jwt权限认证的全流程讲解
JWT
本⽂代码截取⾃实际项⽬。
jwt(Json Web Token),⼀个token,令牌。
简单流程:
⽤户登录成功后,后端返回⼀个token,也就是颁发给⽤户⼀个凭证。之后每⼀次访问,前端都需要携带这个token,后端通过token来解析出当前访问对象。优点
1、⼀定程度上解放了后端,后端不需要再记录当前⽤户是谁,不需要再维护⼀个session,节省了开销。
2、session依赖于cookie,某些场合cookie是⽤不了的,⽐如⽤户浏览器cookie被禁⽤、移动端⽆法存储cookie等。
缺点
1、既然服务器通过token就可以知道当前⽤户是谁,说明其中包含了⽤户信息,有⼀定的泄露风险。
2、因为是⽆状态的,服务器不维持会话,那么每⼀次请求都会重新去数据库读取权限信息,性能有⼀定影响。
(如果想提⾼性能,可以将权限数据存到redis,但是如果⽤redis,就已经失去了jwt的优点,直接⽤普通的token+redis即可)
3、只能校验token是否正确,通过设定过期时间来确定其有效性,不可以⼿动注销。
先说怎么做,再说为什么。
代码
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<security.version>2.3.3.RELEASE</security.version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<jwt.version>0.9.1</jwt.version>
</dependency>
jwt⼯具类
public class JwtTokenUtils implements Serializable {
private static final long serialVersionUID = -3369436201465044824L;
//⽣成token
public static String createToken(String username) {
return Jwts.builder().setSubject(username)
.setExpiration(new Date(System.currentTimeMillis()+ Constants.Jwt.EXPIRATION))
.signWith(SignatureAlgorithm.HS512, Constants.Jwt.KEY)pressWith(CompressionCodecs.GZIP)pact();
}
//获取⽤户名
public static String getUserName(String token) {
return Jwts.parser().setSigningKey(Constants.Jwt.KEY).parseClaimsJws(token).getBody().getSubject();
}
}
涉及常量
public interface Constants {
public interface Jwt{
/**
* 密钥
*/
String KEY = "123123";
/**
* 过期时间
*/
long EXPIRATION = 7200000;
/**
* 请求头
*/
String TOKEN_HEAD = "Authorization";
}
}
实体类和Service
为了减少对实体类的⼊侵,我⼜定义了⼀个对象
原实体类
/**
* ⽤户信息
*
*/
@Data
@TableName("tb_user_info")
public class UserInfo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键id
*/
@TableId(type = IdType.AUTO)
private Integer id;
/**
* 登陆账号
*/
private String loginName;
/**
* 显⽰名
*/
private String dispName;
/**
* 密码
*/
private String password;
/
**
* 状态,1正常,2禁⽤
*/
private Byte status;
/**
* 创建⼈
*/
@TableField(fill = FieldFill.INSERT)
private Integer createBy;
/**
* 更新⼈
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Integer updateBy;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
/**
* 1正常, 0 删除
*/
@TableLogic
private Byte deleted;
}
定义对象
@Data
public class UserSecurity implements UserDetails {
private static final long serialVersionUID = -6777760550924505136L;
private UserInfo userInfo;
private List<String> permissionValues;
public UserSecurity(UserInfo userInfo) {
this.userInfo = userInfo;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
for(String permissionValue : permissionValues) {
if(StringUtils.isEmpty(permissionValue)) continue;
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
authorities.add(authority);
}
return authorities;
}
@Override
public String getPassword() {
Password();
}
@Override
public String getUsername() {
LoginName();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
service
@Service
public class UserInfoServiceImpl implements  UserInfoService, UserDetailsService {
@Autowired
UserInfoMapper userInfoMapper;
@Autowired
PermissionService permissionService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {        QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("login_name", username);
UserInfo userInfo = userInfoMapper.selectOne(queryWrapper);
List<String> rights = permissionService.listPermissions(username);
UserSecurity userSecurity = new UserSecurity(userInfo);
userSecurity.setPermissionValues(rights);
return userSecurity;
}
}
配置类
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserInfoServiceImpl userInfoService;
@Autowired
JwtAuthorizationTokenFilter jwtAuthorizationTokenFilter;
@Autowired
TokenLoginFilter tokenLoginFilter;
@Autowired
JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userInfoService).passwordEncoder(passwordEncoderBean());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and().csrf().disable()
.authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers("/hello").permitAll()
.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers("/swagger-resources/**").permitAll()
spring怎么读取配置
.antMatchers("/v2/*").permitAll()
.antMatchers("/csrf").permitAll()
.
antMatchers("/doc.html").permitAll()
.antMatchers(HttpMethod.OPTIONS, "/**").anonymous()
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.addFilterAt(tokenLoginFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(jwtAuthorizationTokenFilter, TokenLoginFilter.class).httpBasic()
;
}
@Bean
public PasswordEncoder passwordEncoderBean() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
}
过滤器
⼀个负责登录
⼀个负责鉴权
@Component
public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {
@Autowired
PermissionService permissionService;
@Autowired
UserInfoServiceImpl userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {        //获取当前认证成功⽤户权限信息
UsernamePasswordAuthenticationToken authRequest = getAuthentication(request);
//判断如果有权限信息,放到权限上下⽂中
if (authRequest != null) {
}
chain.doFilter(request, response);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
//从header获取token
String token = Header(Constants.Jwt.TOKEN_HEAD);
if (token != null) {
//从token获取⽤户名
String loginName = UserName(token);
//获取权限列表
UserInfo userInfo = userDetailsService.selectByLoginName(loginName);
List<String> permissions = permissionService.listPermissions(loginName);
Collection<GrantedAuthority> authority = new ArrayList<>();
for (String permissionValue : permissions) {
SimpleGrantedAuthority auth = new SimpleGrantedAuthority(permissionValue);
authority.add(auth);
}
return new UsernamePasswordAuthenticationToken(userInfo, token, authority);
}
return null;
}
}
Component
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
public TokenLoginFilter() {
this.setPostOnly(false);
this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/login","POST"));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
//获取表单提交数据
try {
UserInfo user = new ObjectMapper().InputStream(), UserInfo.class);
UsernamePasswordAuthenticationToken authenticationToken = new LoginName(), Password(),null);
AuthenticationManager().authenticate(authenticationToken);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException();
}
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {        UserSecurity
userSecurity = (UserSecurity) Principal();
String token = Username());
ResponseUtils.out(response, R.ok(token));
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
ResponseUtils.out(response, R.fail(ServiceError.LOGIN_FAIL));
}
@Autowired
@Override
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
super.setAuthenticationManager(authenticationManager);
}
}
异常处理
登陆失败异常处理,⾃定义返回类型
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
ResponseUtils.out(response, R.fail(ServiceError.AUTHENTICATION_FAIL));
}
}
⾄于权限和⾓⾊,得看具体的业务,我这边就不贴出来了。
流程分析
总体分析
代码肯定不是凭空出来的,必定有章可循。
⾸先看⽹上的这个图:
最前⾯两个过滤器:密码检验、权限认证。(顺序很重要,鉴权之前总得给⼈家输⼊账号密码的机会吧)
想⼀下传统的session会话模式,登录成功后,服务器维护了session,浏览器得cookie⾥存放了sessionId,⽤户访问的时候浏览器⾃动带上sessionId。
放在以前,服务器端的⼯作都是是框架帮我们做了(原⽣session来⾃于tomcat)。
换成jwt后,需要前端⾃⼰去存储token,后台⾃⼰来解析token。
但是流程还是⼀样的,⽤户登录、获取token、携带token、校验token。。。。。。
登录校验过滤器分析
分析完流程后,就需要看看,如果不⽤jwt,框架⾃⾝是如何实现的。
到UsernamePasswordAuthenticationFilter这个类
可以发现这个类主要是⽤来检验密码⽣成,凭证的。那我们只要继承这个UsernamePasswordAuthenticationFilter类,重写attemptAuthentication,就可以实现登录校验功能。所以我们的代码是这样的:
权限认证过滤器分析
需要看⼀下BasicAuthenticationFilter的源码
其中的关键⽅法:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。