Spring Boot + Spring Security + JWT + 微信小程序登录整合教程
参考文章
用于存储微信小程序登录信息
@Getter
@Setter
@ToString
public class WxAppletAuthenticationToken extends AbstractAuthenticationToken {
private String openId;
private Long userId;
private String sessionKey;
private String rawData;
private String signature;
private String role;
// 使用openid和sessionKey创建一个未验证的token
public WxAppletAuthenticationToken(String openId, String sessionKey, String role) {
super(null);
this.openId = openId;
this.sessionKey = sessionKey;
this.role = role;
}
// 使用openid和sessionKey创建一个已验证的token
public WxAppletAuthenticationToken(String openId, String sessionKey, Long userId, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.openId = openId;
this.userId = userId;
this.sessionKey = sessionKey;
super.setAuthenticated(true);
}
// 使用openid创建一个已验证的token
public WxAppletAuthenticationToken(String openId, Long userId, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.openId = openId;
this.userId = userId;
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return this.openId;
}
@Override
public Object getPrincipal() {
return this.sessionKey;
}
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
if (isAuthenticated) {
throw new IllegalArgumentException(
"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
}
super.setAuthenticated(false);
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
}
}
用于匹配的请求
@Slf4j
public class WxAppletAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public WxAppletAuthenticationFilter(String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {
String method = httpServletRequest.getMethod().toUpperCase();
if (!"POST".equals(method)) {
throw new UnSupportMethodException(method);
}
String contentType = httpServletRequest.getContentType().toLowerCase();
if ("application/json;charset=utf-8".equals(contentType)) {
throw new UnSupportContentTypeException(contentType);
}
// body参数转换为json
StringBuffer sb = new StringBuffer();
String line = null;
BufferedReader reader = httpServletRequest.getReader();
while ((line = reader.readLine()) != null)
sb.append(line);
String jsonString = sb.toString().replaceAll("\\s", "").replaceAll("\n", "");
JSONObject jsonObject = JSONUtil.parseObj(jsonString);
// 取出code
String code = jsonObject.get("code", String.class);
if (ObjectUtil.isEmpty(code)) {
throw new MissingParameterException("code");
}
String role = jsonObject.get("role", String.class);
if (ObjectUtil.isEmpty(role)) {
throw new MissingParameterException("role");
}
JSONObject session = WeChatUtils.code2Session(code);
String openId = session.get("openid", String.class);
String sessionKey = session.get("session_key", String.class);
if (ObjectUtil.isEmpty(openId) || ObjectUtil.isEmpty(sessionKey)) {
throw new RuntimeException("无法获取openId");
}
return this.getAuthenticationManager().authenticate(new WxAppletAuthenticationToken(openId, sessionKey , role));
}
}
真正执行认证逻辑的manager
@Slf4j
@Component
public class WxAppletAuthenticationManager implements AuthenticationManager {
@Resource
private UserService userService;
@Resource
private UserRoleService userRoleService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
WxAppletAuthenticationToken token = null;
if (authentication instanceof WxAppletAuthenticationToken) {
token = (WxAppletAuthenticationToken) authentication;
}
User user = userService.getByOpenId(token.getOpenId());
if (ObjectUtil.isEmpty(user)) {
// 写入角色
Integer roleId = RoleUtils.getRoleId(token.getRole());
if (ObjectUtil.isEmpty(roleId)) {
// 参数的角色不在列表中
throw new RuntimeException("注册失败:" + token.toString());
}
// 注册账号
user = userService.registry(token.getOpenId(), roleId.intValue());
if (ObjectUtil.isEmpty(user)) {
// 注册失败
throw new RuntimeException("注册失败:" + token.toString());
}
}
// 获取权限
List<UserRoleVO> userRoleVOList = userRoleService.getByUserId(user.getUserId());
List<SimpleGrantedAuthority> authorityList = userRoleVOList
.stream()
.map(userRoleVO -> new SimpleGrantedAuthority("ROLE_" + userRoleVO.getRoleName()))
.collect(Collectors.toList());
return new WxAppletAuthenticationToken(user.getOpenId(), user.getUserId(), authorityList);
}
}
在AuthenticationProcessingFilter之前,先验证客户端的token
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Resource
private UserService userService;
@Resource
private UserRoleService userRoleService;
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
String token = httpServletRequest.getHeader("Authorization");
if (!ObjectUtil.isEmpty(token)) {
// 从token中获取openId和userId
String openId = (String) JwtUtils.getClaim(token, "openId");
Long userId = (Long) JwtUtils.getClaim(token, "userId");
if (!ObjectUtil.isEmpty(userId) && !ObjectUtil.isEmpty(openId) && SecurityContextHolder.getContext().getAuthentication() == null) {
log.info("获取" + userId + "的角色");
List<UserRoleVO> userRoleVOList = userRoleService.getByUserId(userId);
List<SimpleGrantedAuthority> authorityList = userRoleVOList
.stream()
.map(userRoleVO -> new SimpleGrantedAuthority("ROLE_" + userRoleVO.getRoleName()))
.collect(Collectors.toList());
// 将token加入安全上下文
SecurityContextHolder.getContext().setAuthentication(new WxAppletAuthenticationToken(openId, userId, authorityList));
}
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
未登录时是处理端点
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletRequest.getRequestDispatcher("/error/auth").forward(httpServletRequest, httpServletResponse);
}
}
登陆成功后的处理器
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
WxAppletAuthenticationToken authenticationToken = (WxAppletAuthenticationToken) authentication;
Map<String, Object> data = new HashMap<>();
data.put("userId", authenticationToken.getUserId());
data.put("openId", authenticationToken.getOpenId());
// 写回token
String token = JwtUtils.getToken(data);
httpServletResponse.setContentType(ContentType.JSON.getValue());
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.getWriter().write(JSONUtil.parseObj(new LoginVO(token)).toStringPretty());
}
}
@RestControllerAdvice
@Order(1)
public class GlobalExceptionHandler {
/**
* 禁止访问
*/
@ExceptionHandler(AccessDeniedException.class)
public Result accessDeniedException() {
return Result.error(ResultCode.NO_PERMISSION);
}
}
Spring Security配置
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.sessionManagement()
// 不创建Session, 使用jwt来管理用户的登录状态
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/error/**", "/swagger-ui.html/**", "/webjars/**", "/v2/**", "/swagger-resources/**").permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(new CustomAuthenticationEntryPoint());
http
.addFilterAt(getWxAppletAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(getJwtAuthenticationTokenFilter(), WxAppletAuthenticationFilter.class);
;
}
@Resource
private WxAppletAuthenticationManager wxAppletAuthenticationManager;
@Bean
public WxAppletAuthenticationFilter getWxAppletAuthenticationFilter() {
WxAppletAuthenticationFilter wxAppletAuthenticationFilter = new WxAppletAuthenticationFilter("/login");
wxAppletAuthenticationFilter.setAuthenticationManager(wxAppletAuthenticationManager);
wxAppletAuthenticationFilter.setAuthenticationSuccessHandler(getCustomAuthenticationSuccessHandler());
return wxAppletAuthenticationFilter;
}
@Bean
public CustomAuthenticationSuccessHandler getCustomAuthenticationSuccessHandler() {
return new CustomAuthenticationSuccessHandler();
}
@Bean
public JwtAuthenticationTokenFilter getJwtAuthenticationTokenFilter() {
return new JwtAuthenticationTokenFilter();
}
}
@Bean
public CustomAuthenticationSuccessHandler getCustomAuthenticationSuccessHandler() {
return new CustomAuthenticationSuccessHandler();
}
@Bean
public JwtAuthenticationTokenFilter getJwtAuthenticationTokenFilter() {
return new JwtAuthenticationTokenFilter();
}
}
## 9. 开启全局方法安全
> 在启动类上加上注解@EnableGlobalMethodSecurity(prePostEnabled = true)