SpringSecurity+token令牌实现微信小程序认证授权

一、什么是token?

1.token就是身份认证的令牌
我们在前端提交账号和密码到服务端去认证,如果认证成功,服务器会颁发给我们一个令牌,也就是token,待我们下一次再访问一个需要认证的页面时,我们不需要再次输入账号和密码去登录验证,而是通过token去验证,token通常存于客户端的内存中,在用户发起请求时从内存中提取token,并添加到请求参数中即可完成身份认证。
2.token的不同类型
小程序中使用的token接近于通常使用的cookie,还有一种token是经过JWT产生的令牌。JWT令牌可能安全性更高,但也都大同小异。

二、为什么要使用token?

1.移动端(手机端)不对cookies进行维护
我们通常在浏览器登录时会用到cookies,它也是一种身份识别码,它也可以完成登录态保持,但是手机端不对cookies进行维护,当然你可以传过来,保存在你的内存中强制让它为你提供认证服务,但是它毕竟不是原配,也不符合移动端应用的特点,我可能在一个公司的多个应用之间进行切换,使用cookies是不利于传输的。
2.token不存于内存中
传统的cookies保存在内存中,这样会带来几个问题。

  1. 内存中的数据是关机即丢失的。
  2. 内存容量通常较小,如果一直将数据存在内存中,很容易导致内存溢出。

三、如何使用token?

要在小程序中获取token,首先得看懂微信官方的小程序登录时序图
小程序登录
这里的openid就是我们说的token,前端不能直接从微信服务器获取openid,必须得从开发者服务器来获取,这主要是出于对appid和appsecret这两个信息安全性的考虑,将其通过前端来传输是不安全的,所以必须存于别人看不见的开发者服务器中。

四、开发过程

1.获取code,并提交给后台

// pages/demo2/demo2.js
Page({
  onLoad: function (options) {
    wx.login({
      success(resn){
        console.log(resn.code)
        wx.request({
          method:'POST',
          url: 'http://localhost:8080/login',
          data:{
           username:'user',
           password:'123',
           miniCode:resn.code
          },
          header:{
              userToken:wx.getStorageSync('userToken'),
              requestCode:'601',
              'content-type':'application/x-www-form-urlencoded'
          },
          success(res){
            wx.setStorageSync('userToken', res.data.userToken)
            console.log(res.data)
          }
        })
      }
    })
  }
})

这里在请求参数中已经添加了账号密码,http://localhost:8080/login是我在后端设定好的登录接口。requestCode:'601’是我设定的一个请求头参数,代表它是登录请求,后台会根据这个code直接跳转到登录过滤器。在后面的代码中可以看到处理方式。
2.获取openid

package com.fiblue.home.login;

import java.io.IOException;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fiblue.home.entity.SuccessJsonMsgPojo;
import com.fiblue.home.entity.TokenDto;
import com.fiblue.home.entity.Wechat;
import com.fiblue.home.msg.SuccessfulJsonMsgProvide;
import com.fiblue.home.service.UserTokenService;

@Component
public class MySuccessHandle extends SimpleUrlAuthenticationSuccessHandler {
	@Autowired
	private SuccessJsonMsgPojo loginSuccessInfo;
	@Autowired
	private Wechat wechat;
	@Autowired
	private ObjectMapper mapper;
    @Autowired
    private UserTokenService userTokenService;
    @Autowired
    private SuccessfulJsonMsgProvide successfulJsonMsgProvide;
	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
			Authentication authentication) throws IOException, ServletException {
		final Logger log = LoggerFactory.getLogger("success日志:");
		String currentUser = authentication.getName();
		logger.info("用户" + currentUser + "登录成功");
		response.setStatus(200);
		response.setContentType("application/json; charset=utf-8");
		// token处理
		String miniCode = request.getParameter("miniCode");
		String url = wechat.getUrl() + "&appid=" + wechat.getAppid() + "&secret=" + wechat.getSecret() + "&js_code="
				    + miniCode + "&grant_type=authorization_code";
		String userName=authentication.getName();
		String userToken=getToken(url).getOpenid();
		userTokenService.updateTokenbyUserName(userName, userToken);//更新token
		userTokenService.updateTokenTimebyUserName(userName);//更新时间
		successfulJsonMsgProvide.printSuccessfulJsonMsg(response, "登录成功", "701", userToken, userName);
		log.info(getToken(url).getOpenid());
	}

	public TokenDto getToken(String url) throws IOException {
		RestTemplate restTemplate = new RestTemplate();
		String result = restTemplate.getForObject(url, String.class);
		TokenDto tokenDto = mapper.readValue(result, TokenDto.class);
		return tokenDto;
	}
}

我们重写了继承并重写了SimpleUrlAuthenticationSuccessHandler,这是一个登录成功后的处理类,它告诉服务器在密码验证成功后该怎么办,默认是跳转到成功页,但是我们这里设定它将openid和其它信息打包成json数据返回给前端并将openid存于数据库中。我已经提前将appid和appsecret等信息存于ymal文件中,直接用一个实体类wechat将其封装起来,这里没必要多说了。
截图

3.token拦截器AuthenticationTokenFilter.class

package com.fiblue.home.filter;

import java.io.IOException;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import com.fiblue.home.entity.Wechat;
import com.fiblue.home.msg.ErrorJsonMsgProvide;
import com.fiblue.home.service.UserService;
import com.fiblue.home.service.UserTokenService;

@Component
public class AuthenticationTokenFilter extends OncePerRequestFilter {
	private final Logger log = LoggerFactory.getLogger("日志:");
	@Autowired
	private UserService userService;
	@Autowired
	private UserTokenService userTokenService;
	@Autowired
	private Wechat wechat;
	@Autowired
	private ErrorJsonMsgProvide errorJsonMsgProvide;
	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws ServletException, IOException {
		String requestCode = request.getHeader("requestCode");// 获取请求码,如果requestCode在头信息中不存在则报错,可以为null
		String userToken = request.getHeader("userToken");// 获取token
		String miniCode = request.getParameter("miniCode");

		String url = wechat.getUrl() + "&appid=" + wechat.getAppid() + "&secret=" + wechat.getSecret() + "&js_code="
				+ miniCode + "&grant_type=authorization_code";
		log.info(url);
		if (userToken != null && !requestCode.equals("601")) {// 如果token存在且不是登录请求
			String userName = userTokenService.loadUserByUsertoken(userToken);
			boolean valid;
			if(userName!=null) {
				valid = userTokenService.checkTokenTimeValidbyUserName(userName);
			}
			else {
				valid=false;//如果用户名不存在,则没必要再进去查询,如果出现空查询则出现错误
			}
			if (userName != null && valid==true) {//非空且有效 如果其中任何一个地方不通过,都应该停止
				UserDetails userDetails = userService.loadUserByUsername(userName);// 根据用户名获取用户对象
				if (userDetails != null) {
					log.info(userDetails.getPassword());//
					UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
							userDetails, userDetails.getPassword(), userDetails.getAuthorities());
					// 设置为已登录
					SecurityContextHolder.getContext().setAuthentication(authentication);
					// 更新时间戳
					userTokenService.updateTokenTimebyUserName(userName);
					log.info("用户:"+userName+" 通过token登录成功");
					userTokenService.updateTokenTimebyUserName(userName);//更新token时间,即续期
					chain.doFilter(request, response);//如果认证通过则doFilter
				}
			} else {// 如果用户名查找不到
				errorJsonMsgProvide.printErrorJsonMsg(response,"token无效", "704");
			}
		}
		if (requestCode.equals("601")) {
			chain.doFilter(request, response);//如果是登录请求也进入doFilter
		}
	}
}

这是一个用在UsernamePasswordAuthenticationFilter之前的过滤器,我们知道UsernamePasswordAuthenticationFilter是一个账号密码处理的一个过滤器,token就是在密码验证之前进行对比,如果token验证通过,就将用户的完整信息封装到UsernamePasswordAuthenticationToken中,并设置成已经验证通过的类型,然后保存于上下文Context中,最后执行接下来的过滤器,也就是chain.doFilter(request, response)。具体是什么情况,大家可以打开UsernamePasswordAuthenticationToken查super.setAuthenticated(true)这个方法是怎么回事就知道了。至于上下文,其实是在后面的过滤器中是通过上下文读取用户信息的,其中,用户是否验证通过这个信息就很重要。

4.更改配置类

package com.fiblue.home.securityconfig;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.fiblue.home.filter.AuthenticationTokenFilter;
import com.fiblue.home.login.MyFailureHandle;
import com.fiblue.home.login.MySuccessHandle;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
	@Autowired
	private MySuccessHandle mySuccessHandle;  
	@Autowired
	private MyFailureHandle myFailureHandle;
	@Autowired 
	private AuthenticationTokenFilter authenticationTokenFilter;
	 @Override
     protected void configure(HttpSecurity http) throws Exception {
         // TODO Auto-generated method stub
         http
         .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
         .csrf().disable()
         .authorizeRequests()
         .antMatchers("/", "/index").permitAll()
         .anyRequest().authenticated()
         .and()
         .formLogin()
         .loginPage("/login").permitAll()
         .loginProcessingUrl("/login")
         .successHandler(mySuccessHandle)
         .failureHandler(myFailureHandle)
         .and()
         .logout().permitAll()
         .and()
         .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
     }
	 @Bean
	 public PasswordEncoder passwordEncoder() {
		 return NoOpPasswordEncoder.getInstance();
	 }
}

我们通过addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)这个方法将token过滤器添加到密码验证过滤器之前,并通过successHandler(mySuccessHandle),改变了密码登录成功后的处理逻辑。
5.测试
1.微信小程序控制台中看到了返回的openid
在这里插入图片描述
2.数据库中也更新了token和时间戳
在这里插入图片描述
3.请求需要授权的数据

// pages/demo4/demo4.js
Page({
  onLoad: function (options) {
       wx.request({
         url: 'http://localhost:8080/test',
         header:{
          'requestCode':'602',
          'content-type':'application/x-www-form-urlencoded',
          'userToken':wx.getStorageSync('userToken')
         },
         success(res){
           console.log(res.data)
         }
       })
  }
})

/test这个地址是需要授权才能访问的,这里通过wx.getStorageSync(‘userToken’)这api接口获取了token并提交了请求。返回结果是成功的。
在这里插入图片描述
教程到此结束,谢谢!

  • 4
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
### 回答1: 要实现微信小程序登录,可以使用Spring Security提供的OAuth 2.0协议实现。以下是基本的步骤: 1. 在微信开放平台中创建小程序,获取AppID和AppSecret。 2. 在Spring Security中配置OAuth 2.0客户端,设置微信小程序的AppID、AppSecret以及授权范围。 3. 创建一个Controller,处理微信小程序登录请求。在该Controller中,使用RestTemplate向微信平台发送请求,获取access_token和openid等信息。 4. 根据openid创建用户信息,并将用户信息存储在数据库中。 5. 在Spring Security中配置自定义的UserDetailsService,根据openid从数据库中查询用户信息并返回。 6. 在Spring Security中配置自定义的AuthenticationProvider,对用户进行认证。 具体实现细节可以参考Spring Security官方文档和示例代码。 ### 回答2: Spring Security可以用于实现微信小程序登录功能。下面是实现该功能的大概步骤: 1. 配置微信小程序开放平台的AppID和AppSecret,并获取sessionKey和openid。 2. 创建一个用于处理登录请求的接口,并在该接口中获取小程序传递的code参数。 3. 使用HTTP请求,向微信服务器发送code和之前配置的AppID、AppSecret,以获取openid和sessionKey。 4. 将获取到的openid和sessionKey存储在数据库中,作为用户的登录凭证。 5. 创建一个用户实体类,并添加相应的字段,比如openid、sessionKey等。 6. 实现一个自定义的UserDetailsService接口,用于根据openid查询用户信息。 7. 创建一个TokenGranter类,用于创建自定义的Token,包含openid和sessionKey等信息。 8. 实现一个自定义的AuthenticationProvider类,用于根据Token进行认证,并授权用户的访问权限。 9. 创建一个自定义的AuthenticationFilter类,用于处理登录请求,并验证用户的Token是否有效。 10. 将上述配置添加到Spring Security的配置类中,并配置相关的路径和权限。 通过上述步骤,我们可以实现微信小程序登录功能。用户通过小程序登录后,系统会根据openid查询用户信息,并通过Token进行认证授权,确保用户可以访问相应的资源。同时,可以根据业务需求,在上述步骤中添加其他的逻辑处理。 ### 回答3: Spring Security是基于Java的安全框架,用于处理应用程序的认证授权功能。要实现微信小程序登录,可以按照以下步骤进行: 1. 配置微信小程序登录:首先,需要在微信开发者平台注册小程序,并获取到小程序的AppID和AppSecret。然后,在Spring Security配置中,配置微信登录认证提供商和回调URL。例如,在`SecurityConfig`类中可以使用`WeChatAuthenticationFilter`来处理微信登录流程和认证。 2. 创建WeChatAuthenticationFilter:继承`AbstractAuthenticationProcessingFilter`类,重写`attemptAuthentication`方法,实现微信登录认证逻辑。在该方法中,将获取到的小程序code发送到微信服务器,通过code获取到微信用户的唯一标识OpenID和会话标识SessionKey。 3. 自定义AuthenticationProvider:创建一个自定义的`AuthenticationProvider`实现类,用于处理微信登录认证逻辑。在该类中,可以根据微信的OpenID进行用户的查询和创建,生成用户的凭证信息,并返回一个实现了`Authentication`接口的认证对象。 4. 处理认证成功和失败的逻辑:在`SuccessfulAuthenticationHandler`中处理认证成功的逻辑,例如生成并返回JWT Token给前端;在`FailureAuthenticationHandler`中处理认证失败的逻辑,例如返回登录失败的提示信息给前端。 5. 配置微信登录接口和拦截器:配置微信登录的接口路径和访问权限,使用`WeChatAuthenticationFilter`拦截微信登录请求,进行认证处理。 通过以上步骤,就可以实现Spring Security微信小程序登录功能。当用户通过微信小程序登录时,将会调用相应的微信登录接口,并经过认证流程完成登录。根据需求可以进行进一步的用户信息补全、鉴权和授权等功能的实现

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值