【若依框架】登录,token,⾃定义session,鉴权等前后端流程解读
背景
之前虽然讲了login,getInfo,getRoutes的三个接⼝,但从设计的⾓度来讲,这3个接⼝并没有完整实现⼀个功能。这⾥重点讲解若依框架对于⾃定义session,token校验,权限验证三个⽅⾯的实现。这些对于⾃⼰实现⼀个简单的后端框架有不错的参考意义
功能说明
登录功能\login及token的⽣成
权限过滤校验
⾃定义session
前端如何配合
可以参考上⼀篇博客
登录及token⽣成
主要解决的是⽤户登录、⽣成token和session的场景
1. 前端
⽤户登录页输⼊username,password,code(验证码),提交login接⼝
注:省略获取验证码接⼝和rememberMe的cookie使⽤
SysLoginController
# 验证码验证
# 2 登录验证
// 该⽅法会去调⽤UserDetailsServiceImpl.loadUserByUsername
authentication = authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(username, password));
注:上⾯会把LoginUser注⼊到Security的Principal中
# 3 ⽣成token
LoginUser loginUser = (LoginUser) Principal();
// ⽣成token
ateToken(loginUser);
TokenService
⽣成token包含了⾃定义session和返回token两⼤部分
1. 使⽤UUID作为sessionId
由于作者使⽤了token这个命名会给阅读带来很⼤困扰,这⾥我们理解为sessionId是最准确的。
public String createToken(LoginUser loginUser)
{
#这⾥的token准确的说法是sessionID
String token = IdUtils.fastUUID();
loginUser.setToken(token);
setUserAgent(loginUser);
refreshToken(loginUser);
Map<String, Object> claims = new HashMap<>();
claims.put(Constants.LOGIN_USER_KEY, token);
return createToken(claims);
}
以sessionId为key,LoginUser为Value保存redis,这个过程本质上就是⾃定义session。
注:忽略⽤户信息填充的过程。
/**
* 刷新令牌有效期
*
* @param loginUser 登录信息
*/
public void refreshToken(LoginUser loginUser)
{
loginUser.setLoginTime(System.currentTimeMillis());
loginUser.LoginTime() + expireTime * MILLIS_MINUTE);
// 根据uuid将loginUser缓存
String userKey = Token());
redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
}
⽣成JwtToken
这⾥产⽣了真正的Jwttoken返回给了前端
1. 没有过期时间
2. 保存了sessionId,⽅便关联session
private String createToken(Map<String, Object> claims)
{
String token = Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS512, secret)pact();
return token;
}
总结:
1. 验证验证码
2. 验证登录信息
3. ⽣成sessionId
4. 以sessionId为key,保存LoginUser的redis,有效期为30分钟
5. ⽣成包含sessionId的token
6. 返回给前端JwtToken
注意4,⽤户登录后在redis实现了⾃定义session,有效期为expireTime。如果redis中的sessionId过期了,就代表⽤户session失效。淡然,如果token不对会跑出校验异常,也⽆法通过,接下来会详细介绍。
接⼝请求时鉴权过滤
安全配置
这⾥配置了可以匿名登录的基本信息
AuthenticationEntryPointImpl 认证失败处理类
LogoutSuccessHandlerImpl 登出处理类
JwtAuthenticationTokenFilter权限过滤器,核⼼
/**
* spring security配置
*
* @author ruoyi
*/
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter
{
/**
* ⾃定义⽤户认证逻辑
*/
@Autowired
private UserDetailsService userDetailsService;
/**
* 认证失败处理类
*/
@Autowired
private AuthenticationEntryPointImpl unauthorizedHandler;
/
**
* 退出处理类
*/
@Autowired
private LogoutSuccessHandlerImpl logoutSuccessHandler;
/**
* token认证过滤器
*/
@Autowired
private JwtAuthenticationTokenFilter authenticationTokenFilter;
/**
* 跨域过滤器
*/
@Autowired
private CorsFilter corsFilter;
/**
* 解决⽆法直接注⼊ AuthenticationManager
*
* @return
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception    {
el表达式获取map的值
return super.authenticationManagerBean();
}
/**
* anyRequest          |  匹配所有请求路径
* anyRequest          |  匹配所有请求路径
* access              |  SpringEl表达式结果为true时可以访问
* anonymous          |  匿名可以访问
* denyAll            |  ⽤户不能访问
* fullyAuthenticated  |  ⽤户完全认证可以访问(⾮remember-me下⾃动登录)
* hasAnyAuthority    |  如果有参数,参数表⽰权限,则其中任何⼀个权限可以访问
* hasAnyRole          |  如果有参数,参数表⽰⾓⾊,则其中任何⼀个⾓⾊可以访问
* hasAuthority        |  如果有参数,参数表⽰权限,则其权限可以访问
* hasIpAddress        |  如果有参数,参数表⽰IP地址,如果⽤户IP和参数匹配,则可以访问
* hasRole            |  如果有参数,参数表⽰⾓⾊,则其⾓⾊可以访问
* permitAll          |  ⽤户可以任意访问
* rememberMe          |  允许通过remember-me登录的⽤户访问
* authenticated      |  ⽤户登录后可访问
*/
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception
{
httpSecurity
// CSRF禁⽤,因为不使⽤session
.csrf().disable()
// 认证失败处理类
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// 基于token,所以不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
// 过滤请求
.authorizeRequests()
// 对于登录login 验证码captchaImage 允许匿名访问
.
antMatchers("/login", "/captchaImage").anonymous()
.antMatchers(
HttpMethod.GET,
"/*.html",
"/**/*.html",
"/**/*.css",
"/**/*.js"
).permitAll()
.antMatchers("/profile/**").anonymous()
.antMatchers("/common/download**").anonymous()
.antMatchers("/common/download/resource**").anonymous()
.
antMatchers("/swagger-ui.html").anonymous()
.antMatchers("/swagger-resources/**").anonymous()
.antMatchers("/webjars/**").anonymous()
.antMatchers("/*/api-docs").anonymous()
.antMatchers("/druid/**").anonymous()
// 除上⾯外的所有请求全部需要鉴权认证
.anyRequest().authenticated()
.and()
.headers().frameOptions().disable();
httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
// 添加JWT filter
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);        // 添加CORS filter
httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);
}
/**
* 强散列哈希加密实现
*/
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder()
{
return new BCryptPasswordEncoder();
}
/**
* ⾝份认证接⼝
* ⾝份认证接⼝
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
}
}
权限过滤JwtAuthenticationTokenFilter权限过滤器
这是⼀个过滤器,所有接⼝请求都会经过这个过滤器(包括login)
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
{
@Autowired
private TokenService tokenService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException
{
LoginUser loginUser = LoginUser(request);
if (StringUtils.isNotNull(loginUser) && StringUtils.Authentication()))
{
tokenService.verifyToken(loginUser);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, Authorit ies());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
}
chain.doFilter(request, response);
}
}
getLoginUser
1. 获取请求头中的Authorization字段的token
2. parseToken验证token有效性,如果token不对这⾥会抛出异常
3. 解析token获取token中的sessionId信息
4. 根据sessionId获取LogInUser并返回

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