天天看点

一文教会你,SpringBoot+SpringSecurity实现微信网页授权登录!

作者:程序猿怪咖
一文教会你,SpringBoot+SpringSecurity实现微信网页授权登录!

1 SpringSecurity简介

SpringSecurity是一个基于Spring全家桶提供的安全访问控制解决方案的安全框架。它是一个功能强大、可高度定制的身份验证和访问控制框架。

JAVA开发者都知道,SpringSecurity是一个为Java程序提供的一个身份验证和授权的框架。

2 SpringBoot+Spring Security实现微信登录

注意一下,因为我们这里要做账号+微信网页授权登录,所以需要提前去微信开放平台准备好你的微信桓公需要的东西,这里就不一一演示了。

下面主要实现其核心代码逻辑。

2.1 Spring Security的WebSecurityConfig配置

package com.test.qyg.yzt.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
@EnableWebSecurity
public class SpringSecurityTestConfig extends WebSecurityConfigurerAdapter {
  @Autowired
  MyAuthenticationnSuccessHandler myAuthenticationSuccessHandler;
  @Autowired MyAuthenticationFailureHandler myAuthenticationFailureHandler;
  @Autowired
  WxAuthenticationnSuccessHandler wxAuthenticationnSuccessHandler;
  @Autowired
  WxAuthenticationFailureHandler wxAuthenticationFailureHandler;
  @Autowired private DataSource dataSource;
  @Autowired
  RedisOneNetUtil redisOneNetUtil;
  @Value("${companyLog.loginPage}")
  private String loginPage;

  @Bean
  public JdbcTokenRepositoryImpl tokenRepository() {
    JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
    tokenRepository.setDataSource(dataSource);
    return tokenRepository;
  }
  @Bean
  UserService customUserService() { // 注册UserDetailsService 的bean
    return new UserServiceImpl();
  }
  @Bean
  UserService weChatUserService() { // 注册UserDetailsService 的bean
    return new WeChatUserServiceImpl();
  }
  /**
   * 此处给AuthenticationManager添加登陆验证的逻辑。
   * 这里添加了两个AuthenticationProvider分别用于用户名密码登陆的验证以及token授权登陆两种方式。
   * 在处理登陆信息的过滤器执行的时候会调用这两个provider进行登陆验证。
   */
  @Override
  public void configure(AuthenticationManagerBuilder auth) throws Exception {
    //用户名和密码登陆
    auth.userDetailsService(customUserService()).passwordEncoder(new BCryptPasswordEncoder());
    //微信openid登陆
    auth.authenticationProvider(weChatAuthenticationProvider());
  }


  /**
   * 微信openid登陆处理
   */
  @Bean
  public WeChatAuthenticationProvider weChatAuthenticationProvider() {
    return new WeChatAuthenticationProvider();
  }

  /**
   * 添加微信openid登陆验证的过滤器
   */
  @Bean
  public WeChatAuthenticationFilter weChatAuthenticationFilter() throws Exception {
    WeChatAuthenticationFilter filter = new WeChatAuthenticationFilter();
    filter.setAuthenticationManager(authenticationManagerBean());
    filter.setAuthenticationSuccessHandler(wxAuthenticationnSuccessHandler);
    filter.setAuthenticationFailureHandler(wxAuthenticationFailureHandler);
    return filter;
  }

  /**
   * 添加用户名和密码登陆验证的过滤器
   */
  @Bean
  public CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
    CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
    filter.setAuthenticationManager(authenticationManagerBean());
    filter.setAuthenticationSuccessHandler(myAuthenticationSuccessHandler);
    filter.setAuthenticationFailureHandler(myAuthenticationFailureHandler);
    return filter;
  }

  /**
   * 配置请求拦截
   *
   */
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    // http: // 192.168.1.225:8080/users/restPwdView?userid=6&taskcode=8grf3B
    HttpMethodFilter filter = new HttpMethodFilter();
    WeChatAuthenticationFilter wechatFilter = weChatAuthenticationFilter();
    CustomAuthenticationFilter customFilter = customAuthenticationFilter();
    ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
    validateCodeFilter.setAuthenticationFailureHandler(myAuthenticationFailureHandler);
    // http.httpBasic()    //httpBasic登录 BasicAuthenticationFilter
    // 必须在注册之后的过滤器之间才能安插过滤器
    http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class)
            .addFilterBefore(wechatFilter, UsernamePasswordAuthenticationFilter.class)
            .addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class)
        .addFilterAfter(validateCodeFilter, HttpMethodFilter.class)
        //表单登录,loginPage为登录请求的url,loginProcessingUrl为表单登录处理的URL
        .formLogin()
        .loginPage(loginPage)
        // 登录需要经过的url请求
        .loginProcessingUrl("/user/login")
        .loginProcessingUrl("/wechat/weChatLogin")
        //.successHandler(myAuthenticationSuccessHandler)
        //.failureHandler(myAuthenticationFailureHandler)
        .and()
        .authorizeRequests()
        .antMatchers(
            loginPage,
            "/comMonAssessScreens",
            "/comMonAssessScreen",
            "/alarmConfiguration/ifCheck",
            "/logOut",
            "/code/image",
            "/meterData/insertElecMeterDataList",
            "/meterDataCreate/*",
            "/common/**",
            "/common/js/**",
            "/wechat/**",
            "/weChatLogin",
            "/weChatLogin.html",
            "/indexV2")
        .permitAll()
        .antMatchers("/static/**")
        .permitAll() // 不拦截静态资源
        .antMatchers("/views/**")
        .permitAll() // 不拦截静态资源
        .antMatchers("/script/**")
        .hasAuthority("ROLE_SuperPermission")
        .antMatchers("/**")
        .fullyAuthenticated()
        // 需要身份认证
        .and()
        // 登出后根据用户读取登出页面
        .logout()
        .logoutUrl("/logOut") // 配置登出请求路径
        .invalidateHttpSession(true)
        .and()
        .headers()
        .frameOptions()
        .sameOrigin()
        .and()
        .rememberMe()
        .tokenRepository(tokenRepository())
        .tokenValiditySeconds(3600) // Token过期时间为一个小时
        .and()
        .csrf()
        .disable() // 注销行为任意访问
        .headers()
        // 增加csp防xss攻击    frame-ancestors 针对frame的加载策略     default-src 针对默认加载策略 object-src 针对插件的加载策略
        .contentSecurityPolicy(
            "frame-ancestors 'self'; default-src 'self' 'unsafe-inline' 'unsafe-eval' *.aliyuncs.com *.baidu.com *.bdimg.com ;object-src 'self'");
  }
}           

2.2 自定义生成token

本系统用账号密码token认证。

package com.test.qyg.yzt.config;

/**
 * @description:用户名和密码验证的token
 */
public class UserAuthenticationToken extends UsernamePasswordAuthenticationToken {
    /**
     *
     */
    private static final long serialVersionUID = -1076492615339314113L;

    public UserAuthenticationToken(Object principal, Object credentials) {
        super(principal, credentials);
    }
    public UserAuthenticationToken(Object principal, Object credentials,
                                   Collection<? extends GrantedAuthority> authorities) {
        super(principal, credentials, authorities);
    }
}           

微信登录token认证。

package com.test.qyg.yzt.config;
/**
 * @description:微信验证的token`
 */
public class VXAuthenticationToken extends UsernamePasswordAuthenticationToken {

    private static final long serialVersionUID = -6231962326068951783L;


    public WeChatAuthenticationToken(Object principal) {
        super(principal, "");
    }


    public WeChatAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
        super(principal, "", authorities);
    }

}           

2.3 创建过滤器的实现

用户名和密码登录验证的过滤器。

package com.test.qyg.yzt.config;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @description:用户名和密码登陆验证的过滤器
 */
public class CustomAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
    public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";

    private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
    private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
    private boolean postOnly = true;
    public CustomAuthenticationFilter() {
        //父类中定义了拦截的请求URL,/login的post请求,直接使用这个配置,也可以自己重写
        super("/user/login");
    }
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException {
        if (postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException(
                    "Authentication method not supported: " + request.getMethod());
        }

        String username = obtainUsername(request);
        String password = obtainPassword(request);

        if (username == null) {
            username = "";
        }

        if (password == null) {
            password = "";
        }

        username = username.trim();

        CustomAuthenticationToken authRequest = new CustomAuthenticationToken(username, password);

        // Allow subclasses to set the "details" property
        setDetails(request,authRequest);

        return this.getAuthenticationManager().authenticate(authRequest);
    }

    protected String obtainPassword(HttpServletRequest request) {
        String password =request.getParameter(passwordParameter);
        return password == null ? "" : password;
    }

    /**
     * Enables subclasses to override the composition of the username, such as by
     * including additional values and a separator.
     *
     * @param request so that request attributes can be retrieved
     *
     * @return the username that will be presented in the <code>Authentication</code>
     * request token to the <code>AuthenticationManager</code>
     */
    protected String obtainUsername(HttpServletRequest request) {
        String username =request.getParameter(usernameParameter);
        return username == null ? "" : username;
    }
    protected void setDetails(HttpServletRequest request,
                              UsernamePasswordAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }
}           

微信登录验证的过滤器。

package com.test.qyg.yzt.config;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @description:微信openid登陆验证的过滤器
 */
public class WeChatAuthenticationFilter extends AbstractAuthenticationProcessingFilter {


    private String openidParameter = "openid";



    public WeChatAuthenticationFilter() {
        super("/wechat/weChatLogin");
        //super.setAuthenticationFailureHandler(new MyAuthenticationFailureHandler());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException {

        if (!request.getMethod().equals(HttpMethod.GET.name())) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }

        String openid = obtainOpenid(request);
        if (openid == null || openid.length() == 0) {
            throw new BadCredentialsException("uid or openid is null.");
        }

        WeChatAuthenticationToken authRequest = new WeChatAuthenticationToken(openid);

        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));

        return this.getAuthenticationManager().authenticate(authRequest);

    }

    protected String obtainOpenid(HttpServletRequest request) {
        String openid = request.getParameter(this.openidParameter);
        return openid == null ? "" : openid.trim();
    }

}           

2.4 创建Provider实现微信登录

package com.sllt.qyg.yzt.config;

import com.mysql.cj.protocol.AuthenticationProvider;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * @description:
 */
public class VXAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    UserWeChatClient userWeChatClient;
    @Autowired
    UserControllerClient userControllerClient;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        if (authentication.isAuthenticated()) {
            return authentication;
        }
        //获取过滤器封装的token信息
        WeChatAuthenticationToken authenticationToken = (WeChatAuthenticationToken) authentication;
        String openid = (String)authenticationToken.getPrincipal();
        //这里逻辑省略,根据自己的逻辑需要修改
        //拿到微信的openid后,去数据库更具openid查询已经管理网站的用户,查到用户信息就直接返回用户信息和权限信息即可
        WeChatAuthenticationToken authenticationResult = new WeChatAuthenticationToken(openid, authorities);

        return authenticationResult;
    }

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

}           

2.5 创建Handler验证微信是否登录成功

如果微信登录成功后跳转到网站首页。

import org.springframework.stereotype.Service;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 *
 * 微信登陆成功后跳转到首页
 */
@Service
public class VXAuthenticationnSuccessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(
            HttpServletRequest request, HttpServletResponse response, Authentication authentication)
            throws IOException, ServletException {
        response.sendRedirect("/index.html");
    }
}           

微信登录验证失败返回错误信息。

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Service;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 *
 * 微信登录验证失败返回错误信息
 */
@Service
public class WxAuthenticationFailureHandler implements AuthenticationFailureHandler {
    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        // 返回json数据
        Map result = new HashMap();
        result.put("wx_success", false);
        result.put("codeRtn", false);
        // 错误信息
        result.put("errorMsg", exception.getMessage());

        String json = objectMapper.writeValueAsString(result);
        response.setContentType("text/json;charset=utf-8");
        response.getWriter().write(json);
    }
}           

2.6 创建Service实现用户逻辑

如果是用户名和密码登录方式就走该逻辑。

import org.springframework.stereotype.Service;

/**
 * 定义UserDetailsService 接口
 */
@Service
public class UserServiceImpl implements UserDetailsService {

    // 授权过程
    @Override
    /** 根据数据库获得用户信息,并且查询出权限,返回带有权限的用户信息。 */
    public User loadUserByUsername(String username) {

        //这里代码逻辑省略,按照izji实际业务逻辑编写去数据库拿用户信息即可
            return new User(user.getUsername(), user.getPassword(), grantedAuthorities);
        } else {
            throw new UsernameNotFoundException("admin: " + username + " do not exist!");
        }
    }
}           

继续阅读