1►JWT博客推荐
阮老师讲得很好了,网址如下:
http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
2►ry是怎么践行JWT的呢?
问题一:不登录的时候有token吗?
答:没有,所以只能在login页面,凡是想跳转其他界面,都被重定向到登录,硬生生让你登录。前端阻拦的代码如下:
问题二:token什么时候生成的?
答:登录的时候生成的,具体代码讲述:
public String login(String username, String password, String code, String uuid)
{boolean captchaOnOff = configService.selectCaptchaOnOff();// 验证码开关if (captchaOnOff){validateCaptcha(username, code, uuid);}// 用户验证Authentication authentication = null;try{// 该方法会去调用UserDetailsServiceImpl.loadUserByUsernameauthentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));}catch (Exception e){if (e instanceof BadCredentialsException){AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));throw new UserPasswordNotMatchException();}else{AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));throw new ServiceException(e.getMessage());}}AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));LoginUser loginUser = (LoginUser) authentication.getPrincipal();recordLoginInfo(loginUser.getUserId());// 生成tokenreturn tokenService.createToken(loginUser);}
最后一句话,他说生成token,于是他执行:
public String createToken(LoginUser loginUser)
{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);}
然后它又是最后一个话在生成token,好家伙,玩我是吧?点进去我们才看到真的生成方法:
private String createToken(Map<String, Object> claims)
{String token = Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, secret).compact();return token;}
现在token生成好了,大家要注意,refreshToken方法已经把当前成功登录的人的信息存到了redis中,前缀是login_tokens: + 当前的tokenId,tokenId是一个uuid。
问题三,用户登录后,发请求是怎么自动带上token的?
登录成功的时候,前端存了一份token在cookie中,登录代码如下:
// 登录Login({ commit }, userInfo) {const username = userInfo.username.trim()const password = userInfo.passwordconst code = userInfo.codeconst uuid = userInfo.uuidreturn new Promise((resolve, reject) => {login(username, password, code, uuid).then(res => {setToken(res.token)commit('SET_TOKEN', res.token)resolve()}).catch(error => {reject(error)})})},
引入眼帘有一个setToken,里面就将token放cookie,代码如下:
export function setToken(token) {return Cookies.set(TokenKey, token)
}
接下来如果要发请求,request.js会自动带上,如下代码:
可以看到,它会去config.headers看看到底要不要,如果我们在请求头指定了不要,那么发请求就不会带上token,否则就会被带上token。
问题四:我再次请求的时候带上了token,后端在哪问我带没带token呢?
package com.ruoyi.framework.security.filter;import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.web.service.TokenService;/*** token过滤器 验证token有效性* * @author ruoyi*/
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
{@Autowiredprivate TokenService tokenService;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws ServletException, IOException
{LoginUser loginUser = tokenService.getLoginUser(request);if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())){tokenService.verifyToken(loginUser);UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));SecurityContextHolder.getContext().setAuthentication(authenticationToken);}chain.doFilter(request, response);}
}
getLoginUser方法总是在试图拿到token。然后验证token是否正确,token有没有过期,然后把用户该有的权限重新设置在上下文中。
该拦截器是在哪里设置的呢?
@Overrideprotected void configure(HttpSecurity httpSecurity) throws Exception
{// 注解标记允许匿名访问的urlExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll());httpSecurity// CSRF禁用,因为不使用session.csrf().disable()// 认证失败处理类.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()// 基于token,所以不需要session.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()// 过滤请求.authorizeRequests()// 对于登录login 注册register 验证码captchaImage 允许匿名访问.antMatchers("/login", "/register", "/captchaImage").anonymous()// 静态资源,可匿名访问.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll().antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()// 除上面外的所有请求全部需要鉴权认证.anyRequest().authenticated().and().headers().frameOptions().disable();// 添加Logout filterhttpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);// 添加JWT filterhttpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);// 添加CORS filterhttpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);}