当前位置:   article > 正文

SpringSecurity+token令牌实现微信小程序认证授权_spring security 过滤器拦截微信小程序登录请求

spring security 过滤器拦截微信小程序登录请求

一、什么是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)
          }
        })
      }
    })
  }
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

这里在请求参数中已经添加了账号密码,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;
	}
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

我们重写了继承并重写了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
		}
	}
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77

这是一个用在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();
	 }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

我们通过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)
         }
       })
  }
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/301626
推荐阅读
相关标签
  

闽ICP备14008679号