代碼地址以下:
http://www.demodashi.com/demo/12736.htmljavascript
具體的登陸說明查看 小程序官方APIhtml
使用idea做爲開發工具,由gradle構建項目,搭建springboot項目,對這塊兒不熟悉的能夠自行去學習,此處很少贅述。下面是核心的配置文件。application.yml中配置springboot默認的參數,application.properties配置自定義的參數,能夠統一配置在一個文件中,依據我的習慣。java
buidle.gradle配置mysql
buildscript { ext { springBootVersion = '1.5.10.RELEASE' } repositories { mavenLocal() maven { url 'http://maven.aliyun.com/nexus/content/groups/public' } mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") } } apply plugin: 'java' apply plugin: 'org.springframework.boot' group = 'xin.yangmj' version = '1.0.1' sourceCompatibility = 1.8 repositories { mavenLocal() maven { url 'http://maven.aliyun.com/nexus/content/groups/public' } mavenCentral() } dependencies { compile('org.springframework.boot:spring-boot-starter-cache') compile('org.springframework.boot:spring-boot-starter-data-redis') compile('org.mybatis.spring.boot:mybatis-spring-boot-starter:1.3.1') compile('org.springframework.boot:spring-boot-starter-security') compile('org.springframework.boot:spring-boot-starter-web') compile('mysql:mysql-connector-java') compile('org.springframework.security:spring-security-test') testCompile('org.springframework.boot:spring-boot-starter-test') compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.7' }
application.ymlweb
logging: level: root: DEBUG spring: datasource: url: jdbc:mysql://localhost/remindme?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8 username: root password: root driver-class-name: com.mysql.jdbc.Driver redis: host: localhost password: port: 6379 mybatis: mapperLocations: classpath:mapper/*.xml configuration: mapUnderscoreToCamelCase: true default-enum-type-handler: org.apache.ibatis.type.EnumOrdinalTypeHandler
application.propertiesredis
# JWT相關配置 jwt.header=Authorization # 過時時間 jwt.expiration=864000 # 注意有一個空格 jwt.tokenHead=Bearer # wechat Auth auth.wechat.sessionHost=https://api.weixin.qq.com/sns/jscode2session auth.wechat.appId=*** auth.wechat.secret=*** auth.wechat.grantType=authorization_code
WebSecurityConfig.javaspring
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtAuthenticationEntryPoint unauthorizedHandler; @Bean public ThirdSessionAuthFilter authenticationTokenFilterBean() throws Exception { return new ThirdSessionAuthFilter(); } @Override protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity // 因爲使用的是JWT,咱們這裏不須要csrf .csrf().disable() .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() // 基於token,因此不須要session .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .authorizeRequests() // 容許對test的無受權訪問 .antMatchers(HttpMethod.GET, "/test").permitAll() // 對於獲取token的rest api要容許匿名訪問 .antMatchers("/auth").permitAll(); // 添加本地地三方session filter httpSecurity .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class); // 禁用緩存 httpSecurity.headers().cacheControl(); } }
ThirdSessionAuthFilter.javasql
@Component public class ThirdSessionAuthFilter extends OncePerRequestFilter { @Value("${jwt.header}") private String tokenHeader; @Value("${jwt.tokenHead}") private String tokenHead; @Autowired private StringRedisTemplate stringRedisTemplate; @Autowired private ConsumerMapper consumerMapper; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { //獲取請求頭部分的Authorization String authHeader = request.getHeader(this.tokenHeader); //若是請求路徑爲微信通知後臺支付結果則不須要token(以後會在具體的controller中,對雙方簽名進行驗證防釣魚) String url = request.getRequestURI().substring(request.getContextPath().length()); if (url.equals("/auth") || url.equals("/test")) { chain.doFilter(request, response); return; } if (null == authHeader || !authHeader.startsWith("Bearer")) { throw new RuntimeException("非法訪問用戶"); } // The part after "Bearer " final String thirdSessionId = authHeader.substring(tokenHead.length()); String wxSessionObj = stringRedisTemplate.opsForValue().get(thirdSessionId); if (StringUtils.isEmpty(wxSessionObj)) { throw new RuntimeException("用戶身份已過時"); } // 設置當前登陸用戶 try (AppContext appContext = new AppContext(wxSessionObj.substring(wxSessionObj.indexOf("#") + 1))) { chain.doFilter(request, response); } } }
AppContext.java數據庫
public class AppContext implements AutoCloseable { private static final ThreadLocal<String> CURRENT_CONSUMER_WECHAT_OPENID = new ThreadLocal<>(); public AppContext(String wechatOpenid) { CURRENT_CONSUMER_WECHAT_OPENID.set(wechatOpenid); } @Override public void close() { CURRENT_CONSUMER_WECHAT_OPENID.remove(); } public static String getCurrentUserWechatOpenId() { return CURRENT_CONSUMER_WECHAT_OPENID.get(); } }
JwtAuthenticationEntryPoint.javaapache
@Component public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable { private static final long serialVersionUID = -8970718410437077606L; @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); } }
WechatAuthProperties.java
@Component public class WechatAuthProperties { @Value("${auth.wechat.sessionHost}") private String sessionHost; @Value("${auth.wechat.appId}") private String appId; @Value("${auth.wechat.secret}") private String secret; @Value("${auth.wechat.grantType}") private String grantType; //省略getter setter }
public class AccountDto { private Long id; private String username; private Long phone; private Gender gender; private String vcode; private String password; private String promotionCode; private String InvitationCode; private String clientAssertion; private String code; //省略 getter setter }
Consumer.java
public class Consumer { private Long id; private String username; private String wechatOpenid; private Long phone; private String nickname; private String avatarUrl; private Gender gender; private String email; private Long lastLoginTime; private Boolean deleted; private Long createdBy; private Long createdAt; private Long updatedBy; private Long updatedAt; // 省略 gettter setter }
Gender.java
public enum Gender { UNKNOW(0, "未知"), MAN(1, "先生"), WOMAN(2, "女士"); private Byte value; private String name; Gender(int value, String name) { this.value = (byte)value; this.name = name; } public Byte getValue() { return this.value; } public String getName() { return this.name; } }
@RestController public class AuthEndpoint { @Value("${jwt.header}") private String tokenHeader; @Value("${jwt.tokenHead}") private String tokenHead; @Autowired private StringRedisTemplate stringRedisTemplate; @Autowired private WechatService wechatService; @GetMapping("/test") public String test() { return "test_success"; } @GetMapping("/testAuth") public String testAuth() { return "testAuth_success"; } @PostMapping("/auth") public ResponseEntity<WechatAuthenticationResponse> createAuthenticationToken(@RequestBody AccountDto accountDto) throws AuthenticationException { WechatAuthenticationResponse jwtResponse = wechatService.wechatLogin(accountDto.getCode()); return ResponseEntity.ok(jwtResponse); } @PostMapping("/updateConsumerInfo") public void updateConsumerInfo(@RequestBody Consumer consumer) { wechatService.updateConsumerInfo(consumer); } }
@Service public class WechatService { private static final Logger LOGGER = LoggerFactory.getLogger(WechatService.class); @Autowired private ConsumerMapper consumerMapper; /** * 服務器第三方session有效時間,單位秒, 默認1天 */ private static final Long EXPIRES = 86400L; private RestTemplate wxAuthRestTemplate = new RestTemplate(); @Autowired private WechatAuthProperties wechatAuthProperties; @Autowired private StringRedisTemplate stringRedisTemplate; public WechatAuthenticationResponse wechatLogin(String code) { WechatAuthCodeResponse response = getWxSession(code); String wxOpenId = response.getOpenid(); String wxSessionKey = response.getSessionKey(); Consumer consumer = new Consumer(); consumer.setWechatOpenid(wxOpenId); loginOrRegisterConsumer(consumer); Long expires = response.getExpiresIn(); String thirdSession = create3rdSession(wxOpenId, wxSessionKey, expires); return new WechatAuthenticationResponse(thirdSession); } public WechatAuthCodeResponse getWxSession(String code) { LOGGER.info(code); String urlString = "?appid={appid}&secret={srcret}&js_code={code}&grant_type={grantType}"; String response = wxAuthRestTemplate.getForObject( wechatAuthProperties.getSessionHost() + urlString, String.class, wechatAuthProperties.getAppId(), wechatAuthProperties.getSecret(), code, wechatAuthProperties.getGrantType()); ObjectMapper objectMapper = new ObjectMapper(); ObjectReader reader = objectMapper.readerFor(WechatAuthCodeResponse.class); WechatAuthCodeResponse res; try { res = reader.readValue(response); } catch (IOException e) { res = null; LOGGER.error("反序列化失敗", e); } LOGGER.info(response); if (null == res) { throw new RuntimeException("調用微信接口失敗"); } if (res.getErrcode() != null) { throw new RuntimeException(res.getErrmsg()); } res.setExpiresIn(res.getExpiresIn() != null ? res.getExpiresIn() : EXPIRES); return res; } public String create3rdSession(String wxOpenId, String wxSessionKey, Long expires) { String thirdSessionKey = RandomStringUtils.randomAlphanumeric(64); StringBuffer sb = new StringBuffer(); sb.append(wxSessionKey).append("#").append(wxOpenId); stringRedisTemplate.opsForValue().set(thirdSessionKey, sb.toString(), expires, TimeUnit.SECONDS); return thirdSessionKey; } private void loginOrRegisterConsumer(Consumer consumer) { Consumer consumer1 = consumerMapper.findConsumerByWechatOpenid(consumer.getWechatOpenid()); if (null == consumer1) { consumerMapper.insertConsumer(consumer); } } public void updateConsumerInfo(Consumer consumer) { Consumer consumerExist = consumerMapper.findConsumerByWechatOpenid(AppContext.getCurrentUserWechatOpenId()); consumerExist.setUpdatedBy(1L); consumerExist.setUpdatedAt(System.currentTimeMillis()); consumerExist.setGender(consumer.getGender()); consumerExist.setAvatarUrl(consumer.getAvatarUrl()); consumerExist.setWechatOpenid(consumer.getWechatOpenid()); consumerExist.setEmail(consumer.getEmail()); consumerExist.setNickname(consumer.getNickname()); consumerExist.setPhone(consumer.getPhone()); consumerExist.setUsername(consumer.getUsername()); consumerMapper.updateConsumer(consumerExist); } }
wx.login() 獲取code,而後攜帶code發送請求到本身服務端,獲取登陸信息。而後 wx.getUserInfo() 獲取用戶的基本信息,例如:暱稱、頭像等,上傳本地服務器保存用戶基本信息。
// 登陸 wx.login({ success: function(res) { if (res.code) { wx.request({ url: "http://localhost:8080/auth", data: { code: res.code }, method: "POST", header: { 'content-type': 'application/json', }, success: function (res) { console.log(res.data.access_token); var token = res.data.access_token; wx.getUserInfo({ success: res => { // 保存用戶信息到服務端 wx.request({ url: "http://localhost:8080/updateConsumerInfo", data: res.userInfo, method: "POST", header: { 'Authorization': 'Bearer ' + token, 'content-type': 'application/json', }, success: function (res) { console.log("success"); }, fail: function (error) { console.log(error); } }) } }) }, fail: function (error) { console.log(error); } }) } else { console.log("error code " + res.errMsg); } } })
刷新微信小程序緩存,編譯使發送請求
發送登陸請求,完成後獲取到 access_token
發送獲取用戶信息請求
小程序請求本地服務器登陸接口
本地服務器請求微信服務器登陸接口
小程序請求本地服務器更新用戶信息接口
redis保存會話信息
mysql數據庫存儲用戶信息
微信小程序登陸JAVA後臺
代碼地址以下:
http://www.demodashi.com/demo/12736.html
注:本文著做權歸做者,由demo大師代發,拒絕轉載,轉載須要做者受權