SpringBoot + Spring Security多种登录方式:账号+微信网页授权登录
一、概述
实现账号用户名+微信网页授权登录集成在Spring Security的思路,最重要的一点是要实现微信登录通过Spring Security安全框架时,不需要验证账号、密码。
二、准备工作
要实现该功能,首先需要掌握Spring Security框架和微信扫码登录接口相关技术,如果对这两块还不太熟悉,可以参考:
1、Springboot + Spring Security实现前后端分离登录认证及权限控制
https://blog.csdn.net/xue317378914/article/details/115318318
2、微信开放平台开发第三方授权登陆:微信扫码登录
https://blog.csdn.net/xue317378914/article/details/115318810
三、项目代码结构
四、Spring Security核心配置:WebSecurityConfig
在WebSecurityConfig中配置了用户名密码登陆的验证以及token授权登陆两种方式,并分别通过不同的拦截器和不同的验证方式来实现该功能。
@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*/
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`*/
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:用户名和密码登陆验证的过滤器*/
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登陆验证的过滤器*/
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* 定义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* 功能描述: 微信登陆成功后操作* @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
* 功能描述: 微信登录验证失败操作
* @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。
作者:小苹果1357
来源:blog.csdn.net/xue317378914/article/
details/115250414
推荐
Java面试题宝典
技术内卷群,一起来学习!!
PS:因为公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。点“在看”支持我们吧!
SpringBoot + Spring Security多种登录方式:账号+微信网页授权登录相关推荐
- Springboot + Spring Security多种登录方式:账号用户名登录+微信网页授权登录
一.概述 实现账号用户名+微信网页授权登录集成在Spring Security的思路,最重要的一点是要实现微信登录通过Spring Security安全框架时,不需要验证账号.密码. 二.准备工作 要 ...
- Spring Boot Security 多种登录方式集成配置思路及方法 账号用户名登录+微信网页授权登录
概述 实现账号用户名+微信网页授权登录集成在Spring Security的思路 前情提要 本思路完全抛弃Spring Security的配置式账号密码登录模式,采用完全独立的Filter.Provi ...
- 微信公众号开发(一) 微信网页授权登录
微信网页授权登录 前期准备 授权登录 获取微信数据 处理授权拒绝 前期准备 1.微信公众号开发,首先要搞一个公众号,开发阶段可以申请一个公众平台测试账号. (进入到微信公众公众平台,找到开发者工具,点 ...
- php微信授权ajax,ajax 实现微信网页授权登录
项目背景 因为项目采用前后端完全分离方案,所以,无法使用常规的微信授权登录作法,需要采用 ajax 实现微信授权登录. 需求分析 因为本人是一个PHPer ,所以,微信开发采用的是 EasyWeCha ...
- java ajax 微信网页授权_ajax 实现微信网页授权登录的方法
AJAX 的 ajax 实现微信网页授权登录的方法 项目背景 因为项目采用前后端完全分离方案,所以,无法使用常规的微信授权登录作法,需要采用 ajax 实现微信授权登录. 需求分析 因为本人是一个ph ...
- 微信网页授权登录的方法
微信网页授权登录官方文档 微信网页授权登录的方法–推荐文章一 微信网页授权登录的方法–推荐文章二 总结: 微信授权方式(scope的属性值控制): 应用授权作用域,snsapi_base (不弹出授权 ...
- 微信网页授权登录java后台实现
建议先阅读微信开发-网页授权登录官方文档: https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_au ...
- php微信登录代理转发,PHP微信网页授权登录
PHP微信网页授权登录 2018年04月10日 15:44:03阅读数:601 namespace Org\WeChat; /** * 微信授权相关接口 */ class Wechat { //高级功 ...
- SpringBoot 实现 微信网页授权登录
SpringBoot简单搭建若是不会,可以看我另一篇文章:https://blog.csdn.net/wang_jing_jing/article/details/115075991 SppringB ...
最新文章
- oc45--多对象内存管理 优化
- frame,iframe,frameset用法和区别
- Codeforces 463E Caisa and Tree
- SpringBoot 集成Mybatis
- 【Tools】TeamViewer安装教程
- 迈克菲实验室:仅42%的网络安全专业人士使用共享威胁情报
- What happens when clicking interaction recor工作中心
- 深入源码理解.NET Core中Startup的注册及运行
- word List 42
- asterisk几个通用函数说明
- 数据库访问的性能问题与瓶颈问题【z】
- 深度学习(七十一)3D CNN时空特征学习
- wordpress功能集成(二):基础知识-wordpress钩子(转)
- ominigraffle 模板_商河盖梁模板安装
- Android点赞头像列表
- 69. Php部分常见问题总结
- CMD常用命令大全(值得收藏)
- Authorization Basic认证 笔记
- Spark CASE WHEN 写法案例
- Task02:学习笔记文本预处理;语言模型;循环神经网络基础
热门文章
- java生成csv文件,excel打开文件乱码问题
- 微信小程序登录、支付流程简介
- (个人)健康科技产品要求及标准大集合
- javascript文字转化成语音
- [摘录]遇见未知的自己(二)
- 小白用canvas画朵花
- 如何进行日常写作训练?
- XML、JSON 与 CSV 文件处理
- 安装scoop报错:iex : 使用“2”个参数调用“DownloadFile”时发生异常:“在 WebClient 请求期间发生异常。”
- Linux文件重命名批量操作(截取特定位置的字符)