spring security5.7 整合微信登录

spring security5.7 整合微信登录

一、前置工作

能正常根据SQL查询到用户即可

二、配置工作

  1. 配置Jackson序列化,用于自定义登录返回状态

@Configuration
public class JsonConfig {
    @Bean
    public ObjectMapper objectMapper() {
        return new ObjectMapper();
    }
}
  1. 配置RestTemplate远程调用,用于调用微信接口

@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
  1. 配置微信相关属性
wechat:
  appid: xxx  #前往微信公众平台查看自己的微信小程序appid
  secret: xxx #前往微信公众平台查看自己的微信小程序secret
  login-url: https://api.weixin.qq.com/sns/jscode2session?appid={appid}&secret={secret}&js_code={js_code}&grant_type={grant_type}
/**
 * 微信配置实体类
 */
@Configuration
@ConfigurationProperties(prefix = "wechat")
@Data
public class WechatConfig {
    private String appid;
    private String secret;
    private String loginUrl;
}
/**
 * 微信请求响应类
 */
@Data
public class WechatLoginInfo {
    private String session_key;
    private String openid;
    private String errcode;
    private String errmsg;
    private String rid;
}
  1. 自定义响应体

@Getter
public class Result<T> {

    private final Integer code;

    private final String message;

    private final T data;

    private Result(HttpStatus httpStatus, T data) {
        this.code = httpStatus.value();
        this.message = httpStatus.getReasonPhrase();
        this.data = data;
    }

    private Result(Integer code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    public static <T> Result<T> of(Integer code, String message, T data) {
        return new Result<>(code, message, data);
    }

    /**
     * 成功
     */
    public static <T> Result<T> success(T data) {
        return new Result<>(HttpStatus.OK, data);
    }

    /**
     * 成功
     */
    public static <T> Result<T> success() {
        return new Result<>(HttpStatus.OK, null);
    }


    /**
     * 失败
     */
    public static Result<String> error() {
        return new Result<>(HttpStatus.INTERNAL_SERVER_ERROR, null);
    }

    /**
     * 失败
     */
    public static Result<String> error(Throwable throwable) {
        return new Result<>(HttpStatus.INTERNAL_SERVER_ERROR, throwable.getMessage());
    }


    /**
     * 未登录
     */
    public static Result<String> unauthorized() {
        return new Result<>(HttpStatus.UNAUTHORIZED, null);
    }

    /**
     * 未登录
     */
    public static Result<String> forbidden() {
        return new Result<>(HttpStatus.FORBIDDEN, null);
    }
}

三、自定义登录响应

/**
 * 认证失败自定义响应
 */
@Slf4j
@Component
public class JsonAuthenticationEntryPoint implements AuthenticationEntryPoint {


    private final ObjectMapper objectMapper;

    public JsonAuthenticationEntryPoint(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        log.error("认证失败", authException);
        System.out.println(request.getRequestURI());
        response.setContentType("application/json; charset=UTF-8");
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.getWriter().write(objectMapper.writeValueAsString(Result.unauthorized()));
    }
}
/**
 * 鉴权失败自定义响应
 */
@Component
@Slf4j
public class JsonAccessDeniedHandler implements AccessDeniedHandler {


    private final ObjectMapper objectMapper;

    public JsonAccessDeniedHandler(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        log.error("权限不足", accessDeniedException);
        response.setContentType("application/json;charset=utf-8");
        response.setStatus(HttpStatus.FORBIDDEN.value());
        response.getWriter().write(objectMapper.writeValueAsString(Result.forbidden()));
    }
}
/**
 * 登录成功自定义处理(过滤器级别)
 */
@Slf4j
@Component
public class JsonAuthenticationSuccessHandler implements AuthenticationSuccessHandler {


    private final ObjectMapper objectMapper;

    public JsonAuthenticationSuccessHandler(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        response.setContentType("application/json;charset=utf-8");
        response.setStatus(HttpStatus.OK.value());
        response.getWriter().write(objectMapper.writeValueAsString(Result.success()));
    }
}
/**
 * 登录失败自定义处理(过滤器级别)
 */
@Component
@Slf4j
public class JsonAuthenticationFailureHandler implements AuthenticationFailureHandler {

    private final ObjectMapper objectMapper;


    public JsonAuthenticationFailureHandler(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.setContentType("application/json;charset=utf-8");
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.getWriter().write(objectMapper.writeValueAsString(Result.unauthorized()));
    }
}

四、创建UserService 查询

public interface UserService extends UserDetailsService {

    Optional<User> findByOpenId(String openId);

    User register(String openId);
}
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserRepository userRepository;


    @Override
    public Optional<User> findByOpenId(String openId) {
        return userRepository.findByOpenId(openId);
    }

    @Override
    public User register(String openId) {
        User user = User.registerByWechat(openId);
        return userRepository.saveAndFlush(user);
    }

    @Override
    public User loadUserByUsername(String username) throws UsernameNotFoundException {
        Optional<User> user = userRepository.findByUsername(username);
        return user.orElse(new User());
    }
}

五、创建微信登录过滤器

不要使用@component,在security config中初始化,否则会导致循环依赖


@Slf4j
public class WeChatAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    private static final HashMap<String, Object> uriVariables = new HashMap<>();

    private static final String CODE_PARAMETER = "code";

    private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/wechat/login", "GET");

    private RestTemplate restTemplate;

    private ObjectMapper objectMapper;

    private WechatConfig wechatConfig;


    public WeChatAuthenticationFilter() {
        super(DEFAULT_ANT_PATH_REQUEST_MATCHER);
    }


    public void setRestTemplate(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    public void setObjectMapper(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    public void setWechatConfig(WechatConfig wechatConfig) {
        this.wechatConfig = wechatConfig;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (!request.getMethod().equals("GET")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
            //校验code
            String code = this.obtainCode(request);
            //发送登录请求
            WechatLoginInfo wechatLoginInfo = wechatLogin(code);
            //创建未认证token
            WechatAuthenticationToken authRequest = WechatAuthenticationToken.unauthenticated(wechatLoginInfo.getOpenid());
            //验证token
            setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }

    /**
     * 微信登录返回用户openid
     */
    @SneakyThrows
    private WechatLoginInfo wechatLogin(String code) {
        this.fillVariables(code);
        String response = restTemplate.getForObject(wechatConfig.getLoginUrl(), String.class, uriVariables);
        log.info("response = {}", response);
        WechatLoginInfo wechatLoginInfo = objectMapper.readValue(response, WechatLoginInfo.class);
        return Optional.of(wechatLoginInfo).filter(obj -> obj.getErrcode() == null).orElseThrow(() -> new AuthenticationServiceException("微信登录异常"));
    }

    /**
     * 封装请求参数
     */
    private void fillVariables(String code) {
        uriVariables.put("appid", wechatConfig.getAppid());
        uriVariables.put("secret", wechatConfig.getSecret());
        uriVariables.put("grant_type", "authorization_code");
        uriVariables.put("js_code", code);
    }

    /**
     * 校验授权码code
     */
    private String obtainCode(HttpServletRequest request) {
        String code = request.getParameter(CODE_PARAMETER);
        return Optional.of(code).orElseThrow(() -> new AuthenticationServiceException("code is empty"));
    }


    @Autowired
    @Override
    public void setAuthenticationManager(AuthenticationManager authenticationManager) {
        super.setAuthenticationManager(authenticationManager);
    }

    protected void setDetails(HttpServletRequest request, WechatAuthenticationToken authRequest) {
        authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
    }

}

六、配置微信登录的Provider


@Slf4j
@Component
public class WechatAuthenticationProvider implements AuthenticationProvider {

    private final UserService userService;

    @Autowired
    public WechatAuthenticationProvider(UserService userService) {
        this.userService = userService;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        //获取openId
        String openId = authentication.getName();
        //根据openId查询用户
        Optional<User> userOptional = userService.findByOpenId(openId);
        //用户存在则获取,不存在则注册
        User user = userOptional.orElseGet(() -> userService.register(openId));
        return WechatAuthenticationToken.authenticated(openId, user.getAuthorities());
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return WechatAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

七、配置微信Token

public class WechatAuthenticationToken extends AbstractAuthenticationToken {

    private final String openId;

    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;


    public WechatAuthenticationToken(String openId) {
        super(null);
        this.openId = openId;
        setAuthenticated(false);
    }


    public WechatAuthenticationToken(String openId, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.openId = openId;
        setAuthenticated(true);
    }


    public static WechatAuthenticationToken unauthenticated(String openId) {
        return new WechatAuthenticationToken(openId);
    }

    public static WechatAuthenticationToken authenticated(String openId, Collection<? extends GrantedAuthority> authorities) {
        return new WechatAuthenticationToken(openId, authorities);
    }

    @Override
    public Object getCredentials() {
        return null;
    }

    @Override
    public Object getPrincipal() {
        return openId;
    }

}

八、配置security config


@EnableWebSecurity(debug = true)
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration {


    private final UserService userService;


    private final RestTemplate restTemplate;


    private final ObjectMapper objectMapper;


    private final WechatConfig wechatConfig;

    private final JsonAuthenticationEntryPoint jsonAuthenticationEntryPoint;

    private final JsonAccessDeniedHandler jsonAccessDeniedHandler;

    private final JsonAuthenticationSuccessHandler jsonAuthenticationSuccessHandler;

    private final JsonAuthenticationFailureHandler jsonAuthenticationFailureHandler;

    private final WechatAuthenticationProvider wechatAuthenticationProvider;

    private final BCryptPasswordEncoder bCryptPasswordEncoder;


    @Autowired
    public SecurityConfiguration(UserService userService,
                                 RestTemplate restTemplate,
                                 ObjectMapper objectMapper,
                                 WechatConfig wechatConfig,
                                 JsonAuthenticationEntryPoint jsonAuthenticationEntryPoint,
                                 JsonAccessDeniedHandler jsonAccessDeniedHandler,
                                 JsonAuthenticationSuccessHandler jsonAuthenticationSuccessHandler,
                                 JsonAuthenticationFailureHandler jsonAuthenticationFailureHandler,
                                 WechatAuthenticationProvider wechatAuthenticationProvider,
                                 BCryptPasswordEncoder bCryptPasswordEncoder) {
        this.userService = userService;
        this.restTemplate = restTemplate;
        this.objectMapper = objectMapper;
        this.wechatConfig = wechatConfig;
        this.jsonAuthenticationEntryPoint = jsonAuthenticationEntryPoint;
        this.jsonAccessDeniedHandler = jsonAccessDeniedHandler;
        this.jsonAuthenticationSuccessHandler = jsonAuthenticationSuccessHandler;
        this.jsonAuthenticationFailureHandler = jsonAuthenticationFailureHandler;
        this.wechatAuthenticationProvider = wechatAuthenticationProvider;
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .formLogin(form -> form
                        .successHandler(jsonAuthenticationSuccessHandler)
                        .failureHandler(jsonAuthenticationFailureHandler)
                )
                .authorizeHttpRequests(request -> request.anyRequest().authenticated())
                .authenticationManager(authenticationManager())
                .exceptionHandling(exception -> exception
                        .authenticationEntryPoint(jsonAuthenticationEntryPoint)
                        .accessDeniedHandler(jsonAccessDeniedHandler)
                )
                .addFilterBefore(weChatAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }

    @Bean
    public AuthenticationManager authenticationManager() {
        return new ProviderManager(wechatAuthenticationProvider, daoAuthenticationProvider());

    }

    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userService);
        daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder);
        return daoAuthenticationProvider;
    }

    @Bean
    public WeChatAuthenticationFilter weChatAuthenticationFilter() {
        WeChatAuthenticationFilter weChatAuthenticationFilter = new WeChatAuthenticationFilter();
        weChatAuthenticationFilter.setObjectMapper(objectMapper);
        weChatAuthenticationFilter.setRestTemplate(restTemplate);
        weChatAuthenticationFilter.setWechatConfig(wechatConfig);
        weChatAuthenticationFilter.setAuthenticationSuccessHandler(jsonAuthenticationSuccessHandler);
        weChatAuthenticationFilter.setAuthenticationFailureHandler(jsonAuthenticationFailureHandler);
        return weChatAuthenticationFilter;
    }
}

附:实体类参考

@Getter
@Setter
@Entity
public class User implements Serializable, UserDetails {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true)
    private String openId;

    private String username;

    private String password;

    private String name;

    private Integer gender;

    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date birthday;

    private Boolean enable;


    @ManyToOne(cascade={CascadeType.REMOVE,CascadeType.REFRESH})
    @JoinColumn(name = "tenant_id")
    @JsonBackReference
    private Tenant tenant;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "user_role",joinColumns =@JoinColumn(name = "user_id"),inverseJoinColumns = @JoinColumn(name = "role_id"))
    @JsonBackReference
    private List<Role> roleList;


    private static final long serialVersionUID = 1L;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return roleList;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return enable;
    }

    public static User registerByWechat(String openId){
        User user = new User();
        user.setOpenId(openId);
        user.setName("游客");
        user.setEnable(true);
        user.setBirthday(new Date());
        return user;
    }

}
@Entity
@Getter
@Setter
public class Role implements Serializable, GrantedAuthority {
    @Id
    private Long id;

    private String name;

    @ManyToMany(mappedBy = "roleList")
    @JsonBackReference
    private List<User> userList;

    private static final long serialVersionUID = 1L;

    @Override
    public String getAuthority() {
        return name;
    }
}

如有问题请评论

  • 4
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
要自定义微信登录,首先需要配置微信开放平台的相关信息,包括app id、app secret、授权回调地址等。然后,可以使用Spring Security提供的OAuth2登录功能进行集成。 具体步骤如下: 1. 添加依赖 ```xml <dependency> <groupId>org.springframework.security.oauth.boot</groupId> <artifactId>spring-security-oauth2-autoconfigure</artifactId> <version>2.1.0.RELEASE</version> </dependency> ``` 2. 配置微信开放平台信息 ```yaml spring: security: oauth2: client: registration: weixin: client-id: <your-client-id> client-secret: <your-client-secret> scope: snsapi_login redirect-uri: <your-redirect-uri> client-name: weixin provider: weixin: authorization-uri: https://open.weixin.qq.com/connect/qrconnect token-uri: https://api.weixin.qq.com/sns/oauth2/access_token user-info-uri: https://api.weixin.qq.com/sns/userinfo user-name-attribute: openid ``` 3. 自定义微信登录处理器 ```java @Component public class WeixinOAuth2UserService extends OAuth2UserService<OAuth2UserRequest, OAuth2User> { @Override public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { String openid = userRequest.getAdditionalParameters().get("openid").toString(); // 根据openid获取用户信息 // ... // 构造OAuth2User对象 return new DefaultOAuth2User(Collections.singleton(new SimpleGrantedAuthority("ROLE_USER")), user.getAttributes(), user.getNameAttributeKey()); } } ``` 4. 配置Spring Security ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private WeixinOAuth2UserService weixinOAuth2UserService; @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/login/**").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .defaultSuccessUrl("/") .permitAll() .and() .oauth2Login() .loginPage("/login") .userInfoEndpoint() .userService(weixinOAuth2UserService) .and() .and() .logout() .logoutUrl("/logout") .permitAll(); } } ``` 以上就是使用Spring Security自定义微信登录的简单步骤,需要注意的是,微信开放平台提供的是OAuth2.0协议,所以需要使用Spring Security OAuth2集成。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值