Spring Security使用(三) 安全框架内使用QQ登录以及不加安全框架使用QQ登录

本文章的代码在第二篇(Spring Security使用(二) 异步登录 | 代码日志 (fanxing.live))的代码上继续完成

腾讯互联

申请地址:QQ互联官网首页

接入教程:网站应用接入概述

创建QQLoginUtil类

application.properties

在配置文件内添加下面几个配置

这些配置的内容在腾讯互联申请的应用就能看到

qq.appid=******
qq.appkey=******
qq.redirect_uri=http://127.0.0.1:8080/QQLogin

QQLoginUtil.java

新建一个 util包 com.example.security2.util,在里面创建这个类

package com.example.security2.util;


import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Component
public class QQLoginUtil {
    @Value("${qq.appid}")
    private String appid = "101513767";

    @Value("${qq.appkey}")
    private String appkey = "b1d978cefcf405388893d8e686d307b0";

    @Value("${qq.redirect_uri}")
    private String redirect_uri = "http://127.0.0.1:8080/QQLogin";

    private ObjectMapper mapper;
    //生成登录链接
    public String getLoginUrl(Object state) throws UnsupportedEncodingException {
        StringBuffer url = new StringBuffer("https://graph.qq.com/oauth2.0/authorize?");
        url.append("response_type=code&");
        url.append("client_id="+appid);
        url.append("&redirect_uri="+ redirect_uri);
        url.append("&state="+state);
        System.out.println(url);
        return url.toString();
    }

    //获取CODE
    public Map getOpenId(String access_token) throws JsonProcessingException {
        String url = "https://graph.qq.com/oauth2.0/me";
        MultiValueMap params = new LinkedMultiValueMap();
        params.add("access_token",access_token);
        params.add("fmt","json");
        String json = httpClient(url,params);
        System.out.println(json);
        if(mapper == null){
            mapper = new ObjectMapper();
        }
        Map<String, Object> tmpMap=mapper.readValue(json, Map.class);
        System.out.println(tmpMap);
        return tmpMap;
    }

    public Map getAccessToken(String code) {
        String url = "https://graph.qq.com/oauth2.0/token";
        MultiValueMap params = new LinkedMultiValueMap();
        params.add("grant_type","authorization_code");
        params.add("client_id",appid);
        params.add("client_secret",appkey);
        params.add("code",code);
        params.add("redirect_uri",redirect_uri);
        params.add("fmt","json");
        String json = httpClient(url,params);
        System.out.println(json);
        if(mapper == null){
            mapper = new ObjectMapper();
        }
        Map<String, Object> tmpMap = null;
        try{
           tmpMap =mapper.readValue(json, Map.class);
        }catch (Exception ex){
            System.out.println("出错了");
        }

        return tmpMap;

    }

    //刷新access_token(自动续期)
    public Map getNewAccessToken(String refresh_token){
        String url = "https://graph.qq.com/oauth2.0/token";
        MultiValueMap params = new LinkedMultiValueMap();
        params.add("grant_type","refresh_token");
        params.add("client_id",appid);
        params.add("client_secret",appkey);
        params.add("refresh_token",refresh_token);
        String json = httpClient(url,params);
        if(mapper == null){
            mapper = new ObjectMapper();
        }
        Map<String, Object> tmpMap = null;
        try{
            tmpMap =mapper.readValue(json, Map.class);
        }catch (Exception ex){
            System.out.println("出错了");
        }

        return tmpMap;
    }
    //获取用户信息
    public Map getUserInfo(String access_token,String openid) throws JsonProcessingException {
        System.out.println("AccessToken="+access_token + ",OpenId="+openid);
        String url = "https://graph.qq.com/user/get_user_info";
        MultiValueMap params = new LinkedMultiValueMap();
        params.add("access_token",access_token);
        params.add("oauth_consumer_key",appid);
        params.add("openid",openid);
        String json = httpClient(url,params);
        if(mapper == null){
            mapper = new ObjectMapper();
        }
        Map<String, Object> tmpMap=mapper.readValue(json, Map.class);
        System.out.println(tmpMap);
        return tmpMap;
    }

    //直接一次获取到个人信息
    public Map getUserInfoPlus(String code){
        try{
            String acode = getAccessToken(code).get("access_toke").toString();

            String openid = getOpenId(acode).get("openid").toString();
            return getUserInfo(acode,openid);
        }catch (Exception ex){
            System.out.println("出问题了");
            return new HashMap();
        }

    }

    //发送请求
    public String httpClient(String url,MultiValueMap<String, String> params){
        RestTemplate client = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        client.getMessageConverters().set(1,
                new StringHttpMessageConverter(StandardCharsets.UTF_8)); // 支持中文编码

        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<MultiValueMap<String, String>>(params, headers);
        ResponseEntity<String> response = client.exchange(url, HttpMethod.POST, requestEntity, String.class);
        return response.getBody();
    }



}

测试是否可以获取到信息

在UserController类下面添加下面方法

@RequestMapping("/QQLogin")
public Map QQLogin(String code) {
    return qqLoginUtil.getUserInfoPlus(code);
}

然后在安全框架配置中 对QQLogin进行忽略,要不然还得登录

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .antMatchers("/js/**","/QQLogin").permitAll()   //就在这一行里面添加

            .anyRequest().authenticated()
            .and().formLogin()
            .loginPage("/logintips")
            .loginProcessingUrl("/check")  //异步校验
            .successHandler(successHandler)
            .failureHandler(failHandle)
            .permitAll()
            .and()
            .exceptionHandling().accessDeniedHandler(accessHandle)
            .and()
            .logout().permitAll();
    http.csrf().disable();
}

接下来测试访问下我们的登录地址,然后使用QQ登录后会打印到浏览器上什么信息

在这里插入图片描述

看上面图片,说明我们可以获取到个人信息了,这说明,我们的QQLoginUtil类写好了。

安全框架

QQAuthenticationFilter

自定义一个身份验证过滤器,这个过滤器用来过滤 /QQLogin这个路径的内容

package com.example.security2;

import com.example.security2.util.QQLoginUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


public class QQAuthenticationFilter extends AbstractAuthenticationProcessingFilter {


    protected QQAuthenticationFilter(String defaultFilterProcessesUrl) {
        super(new AntPathRequestMatcher(defaultFilterProcessesUrl, "GET"));
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {

        String code = request.getParameter("code");
        System.out.println("获取的Code:" + code);

        // 生成验证 authenticationToken,这个传过去code只是把获取的这个想办法传过去
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(code, null);
        // 返回验证结果
        System.out.println("进入校验qq登录");
        return this.getAuthenticationManager().authenticate(authRequest);

    }
}

QQAuthenticationManager

package com.example.security2;

import com.example.security2.pojo.User;
import com.example.security2.util.QQLoginUtil;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

import java.util.*;

public class QQAuthenticationManager implements AuthenticationManager {
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        //这个getname方法就是获取的我们上面传过来的code,如果为null的话,直接抛出token异常
        if(authentication.getName() != null){
            try{
                //QQ登录操作类
                QQLoginUtil qqLoginUtil = new QQLoginUtil();
                
                 Map access_token_get = qqLoginUtil.getAccessToken(authentication.getName());
                 //通过操作类获取到access_token以及refresh_token
                String access_token = access_token_get.get("access_token").toString();
                String refresh_token = access_token_get.get("refresh_token").toString();
                //获取到openid
                String openid = qqLoginUtil.getOpenId(access_token).get("openid").toString();
                //判断是否成功获取到,其实access_token为null的话,直接就会报错了,直接抛出token异常,这一步要与不要都行
                if(access_token == null || openid == null){
                    throw  new BadCredentialsException("Token is invalid");
                }
                
                //直接在这里赋值权限,给它管理员权限
                List<GrantedAuthority> authorities = new ArrayList<>();
                authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
                //将这俩东西保存到一个map中,到handler那里好获取
                Map info = new HashMap();
                info.put("access_token",access_token);
                info.put("openid",openid);
                info.put("refresh_token",refresh_token);
                
                //返回这个认证的token到我们登录成功的Handler里,为什么是登录成功??因为,openid获取到了即使登录成功
                //其实这里少一步,就是这个openid应该给我们数据库的用户来对比一下,看看是否绑定,但这里是QQ直登,所以不需要我们的数据库就可以登录
                return new UsernamePasswordAuthenticationToken(info,null,authorities);
            }catch (Exception ex){
                throw  new BadCredentialsException("Token is invalid");
            }
        }
        throw  new BadCredentialsException("Token is invalid");
    }
}

refresh_token: 把这个也放到map里是因为,如果到时候access_token失效后,可以直接从SecurityContextHolder.getContext().getAuthentication().getName(); 这个方法中把这个map给获取出来,拿着这个refresh_token去刷新一遍

SecurityContextHolder.getContext().getAuthentication().getName();

示例内容

{access_token=9E716E069F480D05D1FB73B258F33242, refresh_token=C4536D2BFE439BCE6B898028909E2438, openid=CE722B59C6E0C995F5FD3D7013A0D271}

QQSusscessHandlerImpl

package com.example.security2;

import com.example.security2.util.QQLoginUtil;
import com.example.security2.vo.ResultVO;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;

public class QQSusscessHandlerImpl implements AuthenticationSuccessHandler {
    ObjectMapper objectMapper;
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {

    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        //登录成功了这里让他转发到一个页面,我们用这个test,让他qq登录成功后直接跳转到这个

        request.getRequestDispatcher("/test").forward(request,response);
    }
}

配置

到这里我们所需要的类已经写完了,只要我们把写的这些装到我们的配置里,那么就可以做测试了

package com.example.security2;

import com.example.security2.service.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.stereotype.Component;

@Configurable
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    CustomUserDetailsService customUserDetailsService;
    @Autowired
    SuccessHandlerImpl successHandler;
    @Autowired
    FailHandleImpl failHandle;

    @Autowired
    AccessHandleImpl accessHandle;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(customUserDetailsService).passwordEncoder(getPasswordEncoder());
    }

    public PasswordEncoder getPasswordEncoder(){
        return  new PasswordEncoder() {
            @Override
            public String encode(CharSequence charSequence) {
                return charSequence.toString();
            }

            @Override
            public boolean matches(CharSequence charSequence, String s) {
                return s.equals(charSequence.toString());
            }
        };
    }
    
    

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/js/**").permitAll()  //.antMatchers("/js/**","/QQLogin").permitAll() 未修改前代码
                .anyRequest().authenticated()
                .and().formLogin()
                .loginPage("/logintips")
                .loginProcessingUrl("/check")  //异步校验
                .successHandler(successHandler)
                .failureHandler(failHandle)
                .permitAll()
                .and()
                .exceptionHandling().accessDeniedHandler(accessHandle)
                .and()
                .logout().permitAll()
        ;
        http.csrf().disable();
        http.addFilterAt(qqAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); //使自定义的过滤器生效
    }

    
    //获取我们的自定义的过滤器,并且在其里面添加Manager校验以及登录成功后的过滤器
    private QQAuthenticationFilter qqAuthenticationFilter(){
        QQAuthenticationFilter authenticationFilter = new QQAuthenticationFilter("/QQLogin"); //这一行是让它拦截/QQLogin路径的地址,也就是我们的回调地址
        authenticationFilter.setAuthenticationManager(new QQAuthenticationManager());
        authenticationFilter.setAuthenticationSuccessHandler(new QQSusscessHandlerImpl());
        return authenticationFilter;
    }
}

测试

修改UserController类下的test

@RequestMapping("/test")
@PreAuthorize("hasRole('ROLE_ADMIN')")
public Object Test(){
    return SecurityContextHolder.getContext().getAuthentication().getName();
}

在这里插入图片描述

由于我们是转发到了test页面,所以路径不会改变,但是内容却变成了我们想要获取的
如果项目里面接入了安全框架,这里应该要跳转我们的首页或者其他页面

欢迎访问:http://www.fanxing.live

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值