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

您所在的位置:网站首页 win10怎么创建账号密码登录微信呢 Springboot + Spring Security多种登录方式:账号用户名登录+微信网页授权登录

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

2024-07-09 13:39| 来源: 网络整理| 查看: 265

一、概述

实现账号用户名+微信网页授权登录集成在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; @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); // tokenRepository.setCreateTableOnStartup(true); // 启动创建表,创建成功后注释掉 return tokenRepository; } @Bean UserDetailsService customUserService() { // 注册UserDetailsService 的bean return new CustomUserServiceImpl(); } @Bean UserDetailsService 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()); } //用户名和密码登陆处理 /*@Bean public CustomAuthenticationProvider customAuthenticationProvider() { return new CustomAuthenticationProvider(); } */ //微信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/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 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 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"); } @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 Authentication * request token to the AuthenticationManager */ 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} */ @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(); } } 七、自定义UserDetailsService

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

/** * @author xxm * @date 2021/3/30 10:39 自定义UserDetailsService 接口 */ @Service public class CustomUserServiceImpl implements UserDetailsService { @Autowired UserControllerClient 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 permissionCodess = userControllerClient.findPermissionByAdminUserName(username); List grantedAuthorities = new ArrayList(); for (String permissionCode : permissionCodess) { if (permissionCode != null && permissionCode != "") { GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(permissionCode); grantedAuthorities.add(grantedAuthority); } } // 返回带有权限的user return 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 { @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(); SysUser user = null; UserWeChatDto uwcDto = new UserWeChatDto(); uwcDto.setOpenId(openid); List uwcList = userWeChatClient.getListByParam(uwcDto); if (null != uwcList && uwcList.size()==1) { UserWeChatDto userWeChatDto = uwcList.get(0); //微信账号已经与网站账号关联 //根据用户id查询用户 user = userControllerClient.getUserById(userWeChatDto.getUserId()); //存放session HttpServletRequest 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 permissionCodess = userControllerClient.findPermissionByAdminUserName(user.getUsername()); List 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; } @Override public 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 { @Override public 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



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3