一、概述

实现账号用户名+微信网页授权登录集成在Spring Security的思路,最重要的一点是要实现微信登录通过Spring Security安全框架时,不需要验证账号、密码。

二、准备工作

要实现该功能,首先需要掌握Spring Security框架和微信扫码登录接口相关技术,如果对这两块还不太熟悉,可以参考我写的相关文章:

1、Springboot + Spring Security实现前后端分离登录认证及权限控制

2、微信开放平台开发第三方授权登陆:微信扫码登录

三、项目代码结构

四、Spring Security核心配置:WebSecurityConfig

在WebSecurityConfig中配置了用户名密码登陆的验证以及token授权登陆两种方式,并分别通过不同的拦截器和不同的验证方式来实现该功能。

/*** @author xxm* @date 2021/3/30 8:40*/
@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {@Autowired MyAuthenticationnSuccessHandler myAuthenticationSuccessHandler;@Autowired MyAuthenticationFailureHandler myAuthenticationFailureHandler;@AutowiredWxAuthenticationnSuccessHandler wxAuthenticationnSuccessHandler;@AutowiredWxAuthenticationFailureHandler wxAuthenticationFailureHandler;@Autowired private DataSource dataSource;@AutowiredRedisOneNetUtil redisOneNetUtil;@Value("${companyLog.loginPage}")private String loginPage;@Beanpublic JdbcTokenRepositoryImpl tokenRepository() {JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();tokenRepository.setDataSource(dataSource);// tokenRepository.setCreateTableOnStartup(true); // 启动创建表,创建成功后注释掉return tokenRepository;}@BeanUserDetailsService customUserService() { // 注册UserDetailsService 的beanreturn new CustomUserServiceImpl();}@BeanUserDetailsService weChatUserService() { // 注册UserDetailsService 的beanreturn new WeChatUserServiceImpl();}/*** 此处给AuthenticationManager添加登陆验证的逻辑。* 这里添加了两个AuthenticationProvider分别用于用户名密码登陆的验证以及token授权登陆两种方式。* 在处理登陆信息的过滤器执行的时候会调用这两个provider进行登陆验证。*/@Overridepublic void configure(AuthenticationManagerBuilder auth) throws Exception {//用户名和密码登陆auth.userDetailsService(customUserService()).passwordEncoder(new BCryptPasswordEncoder());//微信openid登陆auth.authenticationProvider(weChatAuthenticationProvider());}//用户名和密码登陆处理/*@Beanpublic CustomAuthenticationProvider customAuthenticationProvider() {return new CustomAuthenticationProvider();}
*///微信openid登陆处理@Beanpublic WeChatAuthenticationProvider weChatAuthenticationProvider() {return new WeChatAuthenticationProvider();}/*** 添加微信openid登陆验证的过滤器*/@Beanpublic WeChatAuthenticationFilter weChatAuthenticationFilter() throws Exception {WeChatAuthenticationFilter filter = new WeChatAuthenticationFilter();filter.setAuthenticationManager(authenticationManagerBean());filter.setAuthenticationSuccessHandler(wxAuthenticationnSuccessHandler);filter.setAuthenticationFailureHandler(wxAuthenticationFailureHandler);return filter;}/*** 添加用户名和密码登陆验证的过滤器*/@Beanpublic CustomAuthenticationFilter customAuthenticationFilter() throws Exception {CustomAuthenticationFilter filter = new CustomAuthenticationFilter();filter.setAuthenticationManager(authenticationManagerBean());filter.setAuthenticationSuccessHandler(myAuthenticationSuccessHandler);filter.setAuthenticationFailureHandler(myAuthenticationFailureHandler);return filter;}/** 配置请求拦截 */@Overrideprotected void configure(HttpSecurity http) throws Exception {// http: // 192.168.1.225:8080/users/restPwdView?userid=6&taskcode=8grf3BHttpMethodFilter 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/login","/wechat/weChatLogin_epf","/wechat/userLogin","/wechat/userBindLogin","/wechat/userBindGo","/wechat/userBind","/wechat/userUnBind","/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'");}
}

1、configure分别配置两种登录验证方式,用户名和密码登陆使用userDetailsService方法返回的是带有用户名和密码的token,而authenticationProvider方法返回的是含有微信openid的自定义token,分别根据自己的验证逻辑来实现登录验证。

public void configure(AuthenticationManagerBuilder auth) throws Exception {//用户名和密码登陆auth.userDetailsService(customUserService()).passwordEncoder(new BCryptPasswordEncoder());//微信openid登陆auth.authenticationProvider(weChatAuthenticationProvider());}

2、分别定义两个拦截器,各自定义好需要拦截的登录url,并分别处理登录验证逻辑:
CustomAuthenticationFilter 拦截url:"/user/login",WeChatAuthenticationFilter 拦截url:"/wechat/weChatLogin",这两个url都在两个拦截器中有定义。

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")

3、两个拦截器分别实现了自己的登陆成功和失败的处理逻辑

/*** 添加微信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;
}

五、自定义token

1、用户名和密码验证的token,需要账号密码作为验证

/*** @author: xxm* @description:用户名和密码验证的token* @date: 2021/3/10 14:57*/
public class CustomAuthenticationToken extends UsernamePasswordAuthenticationToken {/****/private static final long serialVersionUID = -1076492615339314113L;public CustomAuthenticationToken(Object principal, Object credentials) {super(principal, credentials);}public CustomAuthenticationToken(Object principal, Object credentials,Collection<? extends GrantedAuthority> authorities) {super(principal, credentials, authorities);}
}

2、微信验证的token,只需要一个openid作为验证即可

/*** @author: xxm* @description:微信验证的token* @date: 2021/3/11 14:04*/
public class WeChatAuthenticationToken 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);}}

六、自定义拦截器

1、用户名和密码登陆验证的过滤器,重写了拦截的请求URL,并定义好用户名、密码的参数名称,从请求中获取到用户名、密码,生成CustomAuthenticationToken。拦截器中生成的CustomAuthenticationToken,账号和密码是从前台传过来,它将会和UserDetailsService中返回的CustomAuthenticationToken的账号密码进行对比验证,账号密码是否正确。(UserDetailsService中返回的CustomAuthenticationToken的账号密码是从数据库查出来的)

/*** @author: xxm* @description:用户名和密码登陆验证的过滤器* @date: 2021/3/10 15:04*/
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");}@Overridepublic 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" propertysetDetails(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));}
}

2、微信openid登陆验证的过滤器,重写了拦截的请求URL,并定义好openid的参数名称,从请求中获取到openid,生成WeChatAuthenticationToken。拦截器中生成的WeChatAuthenticationToken,openid是从前台传过来,它将会传递给WeChatAuthenticationProvider,并在该类中验证微信授权openid是否有效(根据openid查询数据库中是否关联用户即可)。

/*** @author: xxm* @description:微信openid登陆验证的过滤器* @date: 2021/3/11 14:58*/
public class WeChatAuthenticationFilter extends AbstractAuthenticationProcessingFilter {private String openidParameter = "openid";public WeChatAuthenticationFilter() {super("/wechat/weChatLogin");//super.setAuthenticationFailureHandler(new MyAuthenticationFailureHandler());}/*** {@inheritDoc}*/@Overridepublic 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();}}

七、自定义UserDetailsService

用户、密码登录采用了该种方式,从数据库查询出用户信息,并且查询出权限,返回带有权限的用户信息

/*** @author xxm* @date 2021/3/30 10:39 自定义UserDetailsService 接口*/
@Service
public class CustomUserServiceImpl implements UserDetailsService {@AutowiredUserControllerClient userControllerClient;// 授权过程@Override/** 根据数据库获得用户信息,并且查询出权限,返回带有权限的用户信息。 */public UserDetails loadUserByUsername(String username) {SysUser user = userControllerClient.getUserInfoByLoginName(username);if (user != null) {HttpServletRequest request =((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();HttpSession session = request.getSession();session.setAttribute("username", username);List<String> permissionCodess = userControllerClient.findPermissionByAdminUserName(username);List<GrantedAuthority> grantedAuthorities = new ArrayList<>();for (String permissionCode : permissionCodess) {if (permissionCode != null && permissionCode != "") {GrantedAuthority grantedAuthority =new SimpleGrantedAuthority(permissionCode);grantedAuthorities.add(grantedAuthority);}}// 返回带有权限的userreturn new User(user.getUsername(), user.getPassword(), grantedAuthorities);} else {throw new UsernameNotFoundException("admin: " + username + " do not exist!");}}
}

八、自定义Provider

微信登录采用了该种方式,根据WeChatAuthenticationFilter 传过来的token信息获取到openid,并根据openid查询微信关联账户,完成验证。

/*** @author: xxm* @description:* @date: 2021/3/11 16:07*/
public class WeChatAuthenticationProvider implements AuthenticationProvider {@AutowiredUserWeChatClient userWeChatClient;@AutowiredUserControllerClient userControllerClient;@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {if (authentication.isAuthenticated()) {return authentication;}//获取过滤器封装的token信息WeChatAuthenticationToken authenticationToken = (WeChatAuthenticationToken) authentication;String openid = (String)authenticationToken.getPrincipal();SysUser user = null;UserWeChatDto uwcDto = new UserWeChatDto();uwcDto.setOpenId(openid);List<UserWeChatDto> uwcList = userWeChatClient.getListByParam(uwcDto);if (null != uwcList && uwcList.size()==1) {UserWeChatDto userWeChatDto = uwcList.get(0);//微信账号已经与网站账号关联//根据用户id查询用户user = userControllerClient.getUserById(userWeChatDto.getUserId());//存放sessionHttpServletRequest request =((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();HttpSession session = request.getSession();session.setAttribute("username", user.getUsername());} else {//微信账号没有关联网站账号throw new BadCredentialsException("微信授权openid无效,请重新登陆");}
//        不通过if (user == null) {throw new BadCredentialsException("微信授权openid无效,请重新登陆");}// 根用户拥有全部的权限List<String> permissionCodess = userControllerClient.findPermissionByAdminUserName(user.getUsername());List<GrantedAuthority> authorities = new ArrayList<>();for (String permissionCode : permissionCodess) {if (permissionCode != null && permissionCode != "") {GrantedAuthority grantedAuthority =new SimpleGrantedAuthority(permissionCode);authorities.add(grantedAuthority);}}WeChatAuthenticationToken authenticationResult = new WeChatAuthenticationToken(openid, authorities);return authenticationResult;}@Overridepublic boolean supports(Class<?> authentication) {return WeChatAuthenticationToken.class.isAssignableFrom(authentication);}}

九、自定义Handler

根据验证成功与失败返回相应数据和操作

/**** @author: xxm* 功能描述: 微信登陆成功后操作* @date: 2021/3/31 13:40* @param: * @return: */
@Service
public class WxAuthenticationnSuccessHandler implements AuthenticationSuccessHandler {@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)throws IOException, ServletException {response.sendRedirect("/index.html");}
}
/**
*
* @author: xxm
* 功能描述: 微信登录验证失败操作
* @date: 2021/3/31 13:40
* @param:
* @return:
*/
@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);
}
}

结束

从准备工作的资料,加上本文的相关代码,就可以实现账号用户名+微信网页授权登录集成在Spring Security

Springboot + Spring Security多种登录方式:账号用户名登录+微信网页授权登录相关推荐

  1. SpringBoot + Spring Security多种登录方式:账号+微信网页授权登录

    一.概述 实现账号用户名+微信网页授权登录集成在Spring Security的思路,最重要的一点是要实现微信登录通过Spring Security安全框架时,不需要验证账号.密码. 二.准备工作 要 ...

  2. Spring Boot Security 多种登录方式集成配置思路及方法 账号用户名登录+微信网页授权登录

    概述 实现账号用户名+微信网页授权登录集成在Spring Security的思路 前情提要 本思路完全抛弃Spring Security的配置式账号密码登录模式,采用完全独立的Filter.Provi ...

  3. 微信公众号开发(一) 微信网页授权登录

    微信网页授权登录 前期准备 授权登录 获取微信数据 处理授权拒绝 前期准备 1.微信公众号开发,首先要搞一个公众号,开发阶段可以申请一个公众平台测试账号. (进入到微信公众公众平台,找到开发者工具,点 ...

  4. php微信授权ajax,ajax 实现微信网页授权登录

    项目背景 因为项目采用前后端完全分离方案,所以,无法使用常规的微信授权登录作法,需要采用 ajax 实现微信授权登录. 需求分析 因为本人是一个PHPer ,所以,微信开发采用的是 EasyWeCha ...

  5. java ajax 微信网页授权_ajax 实现微信网页授权登录的方法

    AJAX 的 ajax 实现微信网页授权登录的方法 项目背景 因为项目采用前后端完全分离方案,所以,无法使用常规的微信授权登录作法,需要采用 ajax 实现微信授权登录. 需求分析 因为本人是一个ph ...

  6. 微信网页授权登录的方法

    微信网页授权登录官方文档 微信网页授权登录的方法–推荐文章一 微信网页授权登录的方法–推荐文章二 总结: 微信授权方式(scope的属性值控制): 应用授权作用域,snsapi_base (不弹出授权 ...

  7. 微信网页授权登录java后台实现

    建议先阅读微信开发-网页授权登录官方文档: https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_au ...

  8. php微信登录代理转发,PHP微信网页授权登录

    PHP微信网页授权登录 2018年04月10日 15:44:03阅读数:601 namespace Org\WeChat; /** * 微信授权相关接口 */ class Wechat { //高级功 ...

  9. SpringBoot 实现 微信网页授权登录

    SpringBoot简单搭建若是不会,可以看我另一篇文章:https://blog.csdn.net/wang_jing_jing/article/details/115075991 SppringB ...

最新文章

  1. python 调用sqldr_sqlldr并发
  2. 使用SGD(Stochastic Gradient Descent)进行大规模机器学习
  3. 山西农信社计算机知识,山西人事考试网 山西农信社考试计算机知识高频考点(二)...
  4. 从用户观点对计算机如何分类,从用户的观点看操作系统是
  5. 使用Vistual Studio N年,推荐2个异常捕获的技巧
  6. Asp.Net MVC1.0正式版发布
  7. 特征筛选10——MIC最大信息系数(有监督筛选)
  8. 为什么程序员愿意加入管理糟糕的创业公司?
  9. JSK-127 进制转换【进制】
  10. python文件处理小方法
  11. jabberd2分析
  12. 通用即插即用监视器驱动下载_大楚云控下载-大楚云控电脑客户端1.0.7 官方版...
  13. Windows的cmd中如何关闭端口
  14. python爬虫——40行代码爬取「笔趣看」全部小说
  15. python三级菜单
  16. 未封装的扩展程序是什么意思_网线上面的标识代表什么意思?网线的九大扩展应用...
  17. crosstool-ng 交叉工具链默认配置 名词解释
  18. u盘坏了数据可以恢复吗?用u盘数据恢复软件
  19. 程序员必学电脑计算机专业英语词汇 11 (125 单词)
  20. NOIP2013 提高组复赛解题报告

热门文章

  1. java的segment_segment 寄存器的真实结构
  2. python爬虫 关键字_包含关键字 python 爬虫 的文章 - 科学空间|Scientific Spaces
  3. 【观察】星环科技:七年磨三剑,积硅步至千里
  4. 手机号码归属地查询[免费]
  5. 《追风筝的人》or《THE KITE RUNNER》
  6. 指定文件名无效或太长,请指定另一文件名
  7. MacOS 使用miniconda 虚拟环境 TensorFlow深度学习环境
  8. Redis入门权威指北
  9. 程序员一直在找的日程安排工具
  10. Android NFC 标签读写Demo与历史漏洞概述