写到这里,我们login 控制器的service 已经可以全部写下去了。登录成功,通过tokenService 返回一个token ,然后封装返回给前端即可。
@Override public UmsAdminTokenBO umsAdminLogin(UmsAdminLoginParam param) { // 通过用户名获取userDetail UserDetails userDetails = this.findUserDetailByUserName(param.getUsername()); // 基本校验用户名和密码 if (!passwordEncoder.matches(param.getPassword(), userDetails.getPassword())) { Asserts.fail("用户名密码错误"); } // 这里暂时不开启权限,后面再修改 UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null); // 将构建的用户信息加入spring security context 上下文 SecurityContextHolder.getContext().setAuthentication(authentication); String token = defaultTokenServer.generateToken(userDetails); return UmsAdminTokenBO.builder().token(token).tokenHeader(jwtConfig.getTokenHeader()).build(); } Security Config接下来。就是配置一个全局的Security Config
public class SecurityConfig extends WebSecurityConfigurerAdapter {}主要还是需要重写configure() 方法。获取一个 registry 实例。将我们的拦截信息加入到里面。
配置开放的路径
配置需要验证的路径。
添加一个JWT默认过滤器,在SpringSecurity 处理之前,将token 进行校验后加入到context 上下文里面。
@Override protected void configure(HttpSecurity http) throws Exception { ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.authorizeRequests(); // 添加开放的路径 for (String url : urlsConfig.getUrls()) { registry.antMatchers(url).permitAll(); } // 允许跨域预请求 registry.antMatchers(HttpMethod.OPTIONS).permitAll(); // 所有的请求都需要身份认证 registry.and() .authorizeRequests() .anyRequest().authenticated() // 关闭csrf 不使用session .and() .csrf() .disable() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 自定义权限拒绝 .and() .exceptionHandling() .accessDeniedHandler(this.customerAccessDenied()) .authenticationEntryPoint(this.customerAuthentication()) // 添加权限拦截器和JWT拦截器,注意,是before .and() .addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); } 自定义过滤器 @Slf4j @Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Autowired private JwtConfig jwtConfig; @Autowired private DefaultTokenServer defaultTokenServer; @Autowired private UserDetailsService userDetailsService; /** * <p> token 过滤器逻辑 * 1、token 必须存在 * 2、toKen 必须正确,未过期。 * 3、若上下文不存在。则往上下文放一个userDetail * <p>author: <a href='http://www.likecs.com/mailto:maruichao52@gmail.com'>MRC</a> * * @param request 请求 * @param response 响应 * @param filterChain 过滤器 * @return void * @since 2020/10/22 **/ @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = request.getHeader(jwtConfig.getTokenHeader()); log.info("doFilterInternal request url={}", request.getRequestURL()); log.info("doFilterInternal request token={}", token); // 请求携带token/则检验这个token是否正确和是否过期 if (!StringUtils.isEmpty(token)) { // 携带的用户名信息 String username = defaultTokenServer.getUserNameFromToken(token); log.info("request token username={}", username); if (StringUtils.isEmpty(username)) { filterChain.doFilter(request, response); } UserDetails userDetails = userDetailsService.loadUserByUsername(username); //校验token是否有效 if (defaultTokenServer.isTokenExpired(token)) { filterChain.doFilter(request, response); } //检查当前上下文是否存在用户信息,若没有则添加 if (SecurityContextHolder.getContext().getAuthentication() == null) { log.info("doFilterInternal getContext = null"); // 将用户信息添加到上下文。说明这个request 是通过的。 UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); log.info("doFilterInternal user:{}", username); SecurityContextHolder.getContext().setAuthentication(authentication); } } // 通过拦截器 filterChain.doFilter(request, response); } }其实我们这里去掉session 以后,我们的客户端对于前端的请求标识、只能通过携带token的方式。
然后我们每一个请求首先会进入JwtAuthenticationTokenFilter 也就是我们上面写的这个。
检查当前请求有没有携带token 要是带了 token 那就检查它,检查成功就从数据库查出来这个人。把这个人注入到我们的SpringSecurity Context 里面。
SpringSecurity 的其他过滤器看到上下文有东西在,就放行~说明是登录后的。
要是没带、或者验证错误~。那上下文也就没有这个用户的信息了。所以这个请求只能返回403
密码问题