Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

基本使用:

添加依赖:



[html]​view plain​​ ​​copy​

​print​​​​?​

  1. <!-- 安全框架 Spring Security -->  
  2. <dependency>  
  3.     <groupId>org.springframework.boot</groupId>  
  4.     <artifactId>spring-boot-starter-security</artifactId>  
  5. </dependency>  
<!-- 安全框架 Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>


我的项目中的使用:

自定义的User对象:



[java]​view plain​​ ​​copy​

​print​​​​?​

  1. /** 
  2.  * 自定义的 User 对象 
  3.  * 此 User 类不是我们的数据库里的用户类,是用来安全服务的 
  4.  */  
  5. public class AnyUser extends User {  
  6.     //import org.springframework.security.core.userdetails.User;  
  7.   
  8.     private Long id;  
  9.   
  10.     private String nickname;  
  11.   
  12.     AnyUser(  
  13.             String username,  
  14.             String password,  
  15.             Collection<? extends GrantedAuthority> authorities  
  16.     ) {  
  17.         super(username, password, authorities);  
  18.     }  
  19.   
  20.     public Long getId() {  
  21.         return id;  
  22.     }  
  23.   
  24.     public void setId(Long id) {  
  25.         this.id = id;  
  26.     }  
  27.   
  28.     public String getNickname() {  
  29.         return nickname;  
  30.     }  
  31.   
  32.     public void setNickname(String nickname) {  
  33.         this.nickname = nickname;  
  34.     }  
  35. }  
/**
* 自定义的 User 对象
* 此 User 类不是我们的数据库里的用户类,是用来安全服务的
*/
public class AnyUser extends User {
//import org.springframework.security.core.userdetails.User;
private Long id;

private String nickname;

AnyUser(
String username,
String password,
Collection<? extends GrantedAuthority> authorities
) {
super(username, password, authorities);
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getNickname() {
return nickname;
}

public void setNickname(String nickname) {
this.nickname = nickname;
}


}

继承UserDetailsService:


首先这里我们需要重写UserDetailsService接口,然后实现该接口中的loadUserByUsername方法,通过该方法查询到对应的用户,这里之所以要实现UserDetailsService接口,是因为在Spring Security中我们配置相关参数需要UserDetailsService类型的数据。



Spring Security 支持把权限划分层次,高层次包含低层次的权限,比如​​ROLE_AMDIN,ROLE_USER​​两个权限,若用户拥有了ROLE_AMDIN权限,那么相当于有了ROLE_USER权限。用户被授权了ADMIN,那么就相当于有其他所有的权限。


[java]​view plain​​ ​​copy​

​print​​​​?​

  1. / 
  2.   自定义 UserDetailsService 
  3.  /  
  4. @Service  
  5. class AnyUserDetailsService implements UserDetailsService {  
  6.   
  7.     private final UserService userService;  
  8.   
  9.     public AnyUserDetailsService(UserService userService){  
  10.         this.userService = userService;  
  11.     }  
  12.   
  13.     @Override  
  14.     public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {  
  15.         com.zhou.model.User user = userService.getByEmail(s);  
  16.         if (user == null){  
  17.             throw new UsernameNotFoundException("用户不存在");  
  18.         }  
  19.         List<SimpleGrantedAuthority> authorities = new ArrayList<>();  
  20.         //对应的权限添加  
  21.         authorities.add(new SimpleGrantedAuthority("ROLE_USER"));  
  22.         AnyUser anyUser = new AnyUser(s, user.getPassword(), authorities);  
  23.         anyUser.setId(user.getId());  
  24.         anyUser.setNickname(user.getNickname());  
  25.         return anyUser;  
  26.     }  
  27.   
  28. }  
/
  • 自定义 UserDetailsService
    */
    @Service
    class AnyUserDetailsService implements UserDetailsService {
    private final UserService userService;
    public AnyUserDetailsService(UserService userService){
    this.userService = userService;
    }
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
    com.zhou.model.User user = userService.getByEmail(s);
    if (user == null){
    throw new UsernameNotFoundException("用户不存在");
    }
    List<SimpleGrantedAuthority> authorities = new ArrayList<>();
    //对应的权限添加
    authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
    AnyUser anyUser = new AnyUser(s, user.getPassword(), authorities);
    anyUser.setId(user.getId());
    anyUser.setNickname(user.getNickname());
    return anyUser;
    }

}

安全控制中心:


[java]​view plain​​ ​​copy​

​print​​​​?​

  1. /** 
  2.  * 安全控制中心 
  3.  */  
  4. @EnableWebSecurity//@EnableWebMvcSecurity 注解开启Spring Security的功能  
  5. public class WebSecurityConfig extends WebSecurityConfigurerAdapter {  
  6.   
  7.     private final UserDetailsService userDetailsService;  
  8.   
  9.     public WebSecurityConfig(AnyUserDetailsService userDetailsService){  
  10.         this.userDetailsService = userDetailsService;  
  11.     }  
  12.   
  13.     @Override  
  14.     protected void configure(AuthenticationManagerBuilder auth) throws Exception {  
  15.         auth.userDetailsService(this.userDetailsService);  
  16.     }  
  17.   
  18.     /** 
  19.      * http.authorizeRequests() 
  20.      .anyRequest().authenticated() 
  21.      .and().formLogin().loginPage("/login") 
  22.      //设置默认登录成功跳转页面 
  23.      .defaultSuccessUrl("/index").failureUrl("/login?error").permitAll() 
  24.      .and() 
  25.      //开启cookie保存用户数据 
  26.      .rememberMe() 
  27.      //设置cookie有效期 
  28.      .tokenValiditySeconds(60 * 60 * 24 * 7) 
  29.      //设置cookie的私钥 
  30.      .key("") 
  31.      .and() 
  32.      .logout() 
  33.      //默认注销行为为logout,可以通过下面的方式来修改 
  34.      .logoutUrl("/custom-logout") 
  35.      //设置注销成功后跳转页面,默认是跳转到登录页面 
  36.      .logoutSuccessUrl("") 
  37.      .permitAll(); 
  38.      * @param http 
  39.      * @throws Exception 
  40.      */  
  41.     @Override  
  42.     protected void configure(HttpSecurity http) throws Exception {  
  43.         http  
  44.                 .authorizeRequests()//authorizeRequests() 定义哪些URL需要被保护、哪些不需要被保护  
  45.                 .antMatchers("/user/**","/news/**").authenticated()  
  46.                 .anyRequest().permitAll()  
  47.                 .and()  
  48.                 .formLogin()  
  49.                 .loginPage("/login")  
  50.                 .defaultSuccessUrl("/user", true)  
  51.                 .permitAll()  
  52.                 .and()  
  53.                 .logout()  
  54.                 .permitAll()  
  55.                 .and().csrf().disable();  
  56.     }  
  57.   
  58. }  
/**
* 安全控制中心
*/
@EnableWebSecurity//@EnableWebMvcSecurity 注解开启Spring Security的功能
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsService userDetailsService;

public WebSecurityConfig(AnyUserDetailsService userDetailsService){
this.userDetailsService = userDetailsService;
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(this.userDetailsService);
}

/**
* http.authorizeRequests()
.anyRequest().authenticated()
.and().formLogin().loginPage("/login")
//设置默认登录成功跳转页面
.defaultSuccessUrl("/index").failureUrl("/login?error").permitAll()
.and()
//开启cookie保存用户数据
.rememberMe()
//设置cookie有效期
.tokenValiditySeconds(60 * 60 * 24 * 7)
//设置cookie的私钥
.key("")
.and()
.logout()
//默认注销行为为logout,可以通过下面的方式来修改
.logoutUrl("/custom-logout")
//设置注销成功后跳转页面,默认是跳转到登录页面
.logoutSuccessUrl("")
.permitAll();
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()//authorizeRequests() 定义哪些URL需要被保护、哪些不需要被保护
.antMatchers("/user/**","/news/**").authenticated()
.anyRequest().permitAll()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/user", true)
.permitAll()
.and()
.logout()
.permitAll()
.and().csrf().disable();
}


}

Spring Security提供了一个过滤器来拦截请求并验证用户身份。如果用户身份认证失败,页面就重定向到/login?error,并且页面中会展现相应的错误信息。若用户想要注销登录,可以通过访问@{/logout}请求,在完成注销之后,页面展现相应的成功消息。

自定义登录成功处理逻辑:

使登陆成功后跳到登录前页面:



[java]​view plain​​ ​​copy​

​print​​​​?​

  1. //处理登录成功的。  
  2. @Component("myAuthenticationSuccessHandler")  
  3. public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {  
  4.   
  5.     @Autowired  
  6.     private ObjectMapper objectMapper;  
  7.   
  8.     @Override  
  9.     public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)  
  10.             throws IOException, ServletException {  
  11.         //什么都不做的话,那就直接调用父类的方法  
  12.         super.onAuthenticationSuccess(request, response, authentication);  
  13.   
  14.         String url=request.getRequestURI();  
  15.   
  16.         //如果是要跳转到某个页面的  
  17.         new DefaultRedirectStrategy().sendRedirect(request, response, url);  
  18.   
  19.     }  
  20. }  
//处理登录成功的。
@Component("myAuthenticationSuccessHandler")
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
@Autowired
private ObjectMapper objectMapper;

@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
//什么都不做的话,那就直接调用父类的方法
super.onAuthenticationSuccess(request, response, authentication);

String url=request.getRequestURI();

//如果是要跳转到某个页面的
new DefaultRedirectStrategy().sendRedirect(request, response, url);

}


}

重新配置安全中心(代码完成之后,修改配置config类代码。添加2个注解,自动注入):



[java]​view plain​​ ​​copy​

​print​​​​?​

  1. @Autowired  
  2. private AuthenticationSuccessHandler myAuthenticationSuccessHandler;  
@Autowired
private AuthenticationSuccessHandler myAuthenticationSuccessHandler;



[java]​view plain​​ ​​copy​

​print​​​​?​

  1. @Override  
  2.     protected void configure(HttpSecurity http) throws Exception {  
  3.         http  
  4.                 .authorizeRequests()//authorizeRequests() 定义哪些URL需要被保护、哪些不需要被保护  
  5.                 .antMatchers("/user/**","/news/**","/blog/manage/**","/blog/create/**").authenticated()  
  6.                 .anyRequest().permitAll()  
  7.                 .and()  
  8.                 .formLogin()  
  9.                 .loginPage("/login")  
  10.                 .successHandler(myAuthenticationSuccessHandler)//登陆成功处理  
  11.                 .permitAll()  
  12.                 .and()  
  13.                 .logout()  
  14.                 .permitAll()  
  15.                 .and().csrf().disable();  
  16.     }  
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()//authorizeRequests() 定义哪些URL需要被保护、哪些不需要被保护
.antMatchers("/user/**","/news/**","/blog/manage/**","/blog/create/**").authenticated()
.anyRequest().permitAll()
.and()
.formLogin()
.loginPage("/login")
.successHandler(myAuthenticationSuccessHandler)//登陆成功处理
.permitAll()
.and()
.logout()
.permitAll()
.and().csrf().disable();
}


QQ登录实现:


准备工作:

为了方便各位测试,这里直接提供一个可以使用的:


APP ID:101386962


APP Key:2a0f820407df400b84a854d054be8b6a


回调地址:​​http://www.ictgu.cn/login/qq​


在SpringBoot中对SpringSecurity的基本使用_SpringBoot


提醒:因为回调地址不是 http://localhost ,所以在启动我提供的demo时,需要在host文件中添加一行:127.0.0.1 www.ictgu.cn



后端详解:

1、自定义 QQAuthenticationFilter 继承 AbstractAuthenticationProcessingFilter:



[java]​view plain​​ ​​copy​

​print​​​​?​

  1. import com.alibaba.fastjson.JSON;  
  2. import org.jsoup.Jsoup;  
  3. import org.jsoup.nodes.Document;  
  4. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;  
  5. import org.springframework.security.core.Authentication;  
  6. import org.springframework.security.core.AuthenticationException;  
  7. import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;  
  8. import org.springframework.security.web.util.matcher.AntPathRequestMatcher;  
  9.   
  10. import javax.servlet.ServletException;  
  11. import javax.servlet.http.HttpServletRequest;  
  12. import javax.servlet.http.HttpServletResponse;  
  13. import java.io.IOException;  
  14. import java.util.regex.Matcher;  
  15. import java.util.regex.Pattern;  
  16.   
  17. public class QQAuthenticationFilter extends AbstractAuthenticationProcessingFilter {  
  18.     private final static String CODE = "code";  
  19.   
  20.     /** 
  21.      * 获取 Token 的 API 
  22.      */  
  23.     private final static String accessTokenUri = "https://graph.qq.com/oauth2.0/token";  
  24.   
  25.     /** 
  26.      * grant_type 由腾讯提供 
  27.      */  
  28.     private final static String grantType = "authorization_code";  
  29.   
  30.     /** 
  31.      * client_id 由腾讯提供 
  32.      */  
  33.     public static final String clientId = "101386962";  
  34.   
  35.     /** 
  36.      * client_secret 由腾讯提供 
  37.      */  
  38.     private final static String clientSecret = "2a0f820407df400b84a854d054be8b6a";  
  39.   
  40.     /** 
  41.      * redirect_uri 腾讯回调地址 
  42.      */  
  43.     private final static String redirectUri = "http://www.ictgu.cn/login/qq";  
  44.   
  45.     /** 
  46.      * 获取 OpenID 的 API 地址 
  47.      */  
  48.     private final static String openIdUri = "https://graph.qq.com/oauth2.0/me?access_token=";  
  49.   
  50.     /** 
  51.      * 获取 token 的地址拼接 
  52.      */  
  53.     private final static String TOKEN_ACCESS_API = "%s?grant_type=%s&client_id=%s&client_secret=%s&code=%s&redirect_uri=%s";  
  54.   
  55.     public QQAuthenticationFilter(String defaultFilterProcessesUrl) {  
  56.         super(new AntPathRequestMatcher(defaultFilterProcessesUrl, "GET"));  
  57.     }  
  58.   
  59.     @Override  
  60.     public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {  
  61.         String code = request.getParameter(CODE);  
  62.         String tokenAccessApi = String.format(TOKEN_ACCESS_API, accessTokenUri, grantType, clientId, clientSecret, code, redirectUri);  
  63.         QQToken qqToken = this.getToken(tokenAccessApi);  
  64.         if (qqToken != null){  
  65.             String openId = getOpenId(qqToken.getAccessToken());  
  66.             if (openId != null){  
  67.                 // 生成验证 authenticationToken  
  68.                 UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(qqToken.getAccessToken(), openId);  
  69.                 // 返回验证结果  
  70.                 return this.getAuthenticationManager().authenticate(authRequest);  
  71.             }  
  72.         }  
  73.         return null;  
  74.     }  
  75.   
  76.     private QQToken getToken(String tokenAccessApi) throws IOException{  
  77.         Document document = Jsoup.connect(tokenAccessApi).get();  
  78.         String tokenResult = document.text();  
  79.         String[] results = tokenResult.split("&");  
  80.         if (results.length == 3){  
  81.             QQToken qqToken = new QQToken();  
  82.             String accessToken = results[0].replace("access_token=", "");  
  83.             int expiresIn = Integer.valueOf(results[1].replace("expires_in=", ""));  
  84.             String refreshToken = results[2].replace("refresh_token=", "");  
  85.             qqToken.setAccessToken(accessToken);  
  86.             qqToken.setExpiresIn(expiresIn);  
  87.             qqToken.setRefresh_token(refreshToken);  
  88.             return qqToken;  
  89.         }  
  90.         return null;  
  91.     }  
  92.   
  93.     private String getOpenId(String accessToken) throws IOException{  
  94.         String url = openIdUri + accessToken;  
  95.         Document document = Jsoup.connect(url).get();  
  96.         String resultText = document.text();  
  97.         Matcher matcher = Pattern.compile("\"openid\":\"(.*?)\"").matcher(resultText);  
  98.         if (matcher.find()){  
  99.             return matcher.group(1);  
  100.         }  
  101.         return null;  
  102.     }  
  103.   
  104.     class QQToken {  
  105.   
  106.         /** 
  107.          * token 
  108.          */  
  109.         private String accessToken;  
  110.   
  111.         /** 
  112.          * 有效期 
  113.          */  
  114.         private int expiresIn;  
  115.   
  116.         /** 
  117.          * 刷新时用的 token 
  118.          */  
  119.         private String refresh_token;  
  120.   
  121.         String getAccessToken() {  
  122.             return accessToken;  
  123.         }  
  124.   
  125.         void setAccessToken(String accessToken) {  
  126.             this.accessToken = accessToken;  
  127.         }  
  128.   
  129.         public int getExpiresIn() {  
  130.             return expiresIn;  
  131.         }  
  132.   
  133.         void setExpiresIn(int expiresIn) {  
  134.             this.expiresIn = expiresIn;  
  135.         }  
  136.   
  137.         public String getRefresh_token() {  
  138.             return refresh_token;  
  139.         }  
  140.   
  141.         void setRefresh_token(String refresh_token) {  
  142.             this.refresh_token = refresh_token;  
  143.         }  
  144.     }  
  145. }  
import com.alibaba.fastjson.JSON;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
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.util.matcher.AntPathRequestMatcher;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import java.io.IOException;

import java.util.regex.Matcher;

import java.util.regex.Pattern;

public class QQAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

private final static String CODE = "code";

/**
* 获取 Token 的 API
*/
private final static String accessTokenUri = "https://graph.qq.com/oauth2.0/token";

/**
* grant_type 由腾讯提供
*/
private final static String grantType = "authorization_code";

/**
* client_id 由腾讯提供
*/
public static final String clientId = "101386962";

/**
* client_secret 由腾讯提供
*/
private final static String clientSecret = "2a0f820407df400b84a854d054be8b6a";

/**
* redirect_uri 腾讯回调地址
*/
private final static String redirectUri = "http://www.ictgu.cn/login/qq";

/**
* 获取 OpenID 的 API 地址
*/
private final static String openIdUri = "https://graph.qq.com/oauth2.0/me?access_token=";

/**
* 获取 token 的地址拼接
*/
private final static String TOKEN_ACCESS_API = "%s?grant_type=%s&client_id=%s&client_secret=%s&code=%s&redirect_uri=%s";

public 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);
String tokenAccessApi = String.format(TOKEN_ACCESS_API, accessTokenUri, grantType, clientId, clientSecret, code, redirectUri);
QQToken qqToken = this.getToken(tokenAccessApi);
if (qqToken != null){
String openId = getOpenId(qqToken.getAccessToken());
if (openId != null){
// 生成验证 authenticationToken
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(qqToken.getAccessToken(), openId);
// 返回验证结果
return this.getAuthenticationManager().authenticate(authRequest);
}
}
return null;
}

private QQToken getToken(String tokenAccessApi) throws IOException{
Document document = Jsoup.connect(tokenAccessApi).get();
String tokenResult = document.text();
String[] results = tokenResult.split("&");
if (results.length == 3){
QQToken qqToken = new QQToken();
String accessToken = results[0].replace("access_token=", "");
int expiresIn = Integer.valueOf(results[1].replace("expires_in=", ""));
String refreshToken = results[2].replace("refresh_token=", "");
qqToken.setAccessToken(accessToken);
qqToken.setExpiresIn(expiresIn);
qqToken.setRefresh_token(refreshToken);
return qqToken;
}
return null;
}

private String getOpenId(String accessToken) throws IOException{
String url = openIdUri + accessToken;
Document document = Jsoup.connect(url).get();
String resultText = document.text();
Matcher matcher = Pattern.compile("\"openid\":\"(.*?)\"").matcher(resultText);
if (matcher.find()){
return matcher.group(1);
}
return null;
}

class QQToken {

/**
* token
*/
private String accessToken;

/**
* 有效期
*/
private int expiresIn;

/**
* 刷新时用的 token
*/
private String refresh_token;

String getAccessToken() {
return accessToken;
}

void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}

public int getExpiresIn() {
return expiresIn;
}

void setExpiresIn(int expiresIn) {
this.expiresIn = expiresIn;
}

public String getRefresh_token() {
return refresh_token;
}

void setRefresh_token(String refresh_token) {
this.refresh_token = refresh_token;
}
}


}

说明:Filter 过滤时执行的方法是 doFilter(),由于 QQAuthenticationFilter 继承了 AbstractAuthenticationProcessingFilter,所以过滤时使用的是父类的doFilter() 方法。

说明:doFilter()方法中,有一步是 attemptAuthentication(request, response) 即为 QQAuthenticationFilter 中实现的方法。这个方法中调用了 this.getAuthenticationManager().authenticate(authRequest),这里自定义了类 QQAuthenticationManager,代码如下:




[java]​view plain​​ ​​copy​

​print​​​​?​

  1. import com.alibaba.fastjson.JSON;  
  2. import com.alibaba.fastjson.JSONObject;  
  3. import com.zhou.model.User;  
  4. import org.jsoup.Jsoup;  
  5. import org.jsoup.nodes.Document;  
  6. import org.springframework.security.authentication.AuthenticationManager;  
  7. import org.springframework.security.authentication.BadCredentialsException;  
  8. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;  
  9. import org.springframework.security.core.Authentication;  
  10. import org.springframework.security.core.AuthenticationException;  
  11. import org.springframework.security.core.GrantedAuthority;  
  12. import org.springframework.security.core.authority.SimpleGrantedAuthority;  
  13. import java.io.IOException;  
  14. import java.util.ArrayList;  
  15. import java.util.List;  
  16.   
  17. import static com.zhou.config.qq.QQAuthenticationFilter.clientId;  
  18.   
  19. public class QQAuthenticationManager implements AuthenticationManager {  
  20.     private static final List<GrantedAuthority> AUTHORITIES = new ArrayList<>();  
  21.   
  22.     /** 
  23.      * 获取 QQ 登录信息的 API 地址 
  24.      */  
  25.     private final static String userInfoUri = "https://graph.qq.com/user/get_user_info";  
  26.   
  27.     /** 
  28.      * 获取 QQ 用户信息的地址拼接 
  29.      */  
  30.     private final static String USER_INFO_API = "%s?access_token=%s&oauth_consumer_key=%s&openid=%s";  
  31.   
  32.     static {  
  33.         AUTHORITIES.add(new SimpleGrantedAuthority("ROLE_USER"));  
  34.     }  
  35.   
  36.     @Override  
  37.     public Authentication authenticate(Authentication auth) throws AuthenticationException {  
  38.         if (auth.getName() != null && auth.getCredentials() != null) {  
  39.             User user = null;  
  40.             try {  
  41.                 user = getUserInfo(auth.getName(), (String) (auth.getCredentials()));  
  42.             } catch (Exception e) {  
  43.                 e.printStackTrace();  
  44.             }  
  45.             return new UsernamePasswordAuthenticationToken(user,  
  46.                     null, AUTHORITIES);  
  47.         }  
  48.         throw new BadCredentialsException("Bad Credentials");  
  49.     }  
  50.   
  51.     private User getUserInfo(String accessToken, String openId) throws Exception {  
  52.         String url = String.format(USER_INFO_API, userInfoUri, accessToken, clientId, openId);  
  53.         Document document;  
  54.         try {  
  55.             document = Jsoup.connect(url).get();  
  56.         } catch (IOException e) {  
  57.             throw new BadCredentialsException("Bad Credentials!");  
  58.         }  
  59.         String resultText = document.text();  
  60.         JSONObject json = JSON.parseObject(resultText);  
  61.   
  62.         User user = new User();  
  63.         user.setNickname(json.getString("nickname"));  
  64.         user.setEmail("暂无。。。。");  
  65.         //user.setGender(json.getString("gender"));  
  66.         //user.setProvince(json.getString("province"));  
  67.         //user.setYear(json.getString("year"));  
  68.         user.setAvatar(json.getString("figureurl_qq_2"));  
  69.   
  70.         return user;  
  71.     }  
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.zhou.model.User;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
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.io.IOException;
import java.util.ArrayList;
import java.util.List;

import static com.zhou.config.qq.QQAuthenticationFilter.clientId;

public class QQAuthenticationManager implements AuthenticationManager {

private static final List<GrantedAuthority> AUTHORITIES = new ArrayList<>();

/**
* 获取 QQ 登录信息的 API 地址
*/
private final static String userInfoUri = "https://graph.qq.com/user/get_user_info";

/**
* 获取 QQ 用户信息的地址拼接
*/
private final static String USER_INFO_API = "%s?access_token=%s&oauth_consumer_key=%s&openid=%s";

static {
AUTHORITIES.add(new SimpleGrantedAuthority("ROLE_USER"));
}

@Override
public Authentication authenticate(Authentication auth) throws AuthenticationException {
if (auth.getName() != null && auth.getCredentials() != null) {
User user = null;
try {
user = getUserInfo(auth.getName(), (String) (auth.getCredentials()));
} catch (Exception e) {
e.printStackTrace();
}
return new UsernamePasswordAuthenticationToken(user,
null, AUTHORITIES);
}
throw new BadCredentialsException("Bad Credentials");
}

private User getUserInfo(String accessToken, String openId) throws Exception {
String url = String.format(USER_INFO_API, userInfoUri, accessToken, clientId, openId);
Document document;
try {
document = Jsoup.connect(url).get();
} catch (IOException e) {
throw new BadCredentialsException("Bad Credentials!");
}
String resultText = document.text();
JSONObject json = JSON.parseObject(resultText);

User user = new User();
user.setNickname(json.getString("nickname"));
user.setEmail("暂无。。。。");
//user.setGender(json.getString("gender"));
//user.setProvince(json.getString("province"));
//user.setYear(json.getString("year"));
user.setAvatar(json.getString("figureurl_qq_2"));

return user;
}</pre>说明:QQAuthenticationManager 的作用是通过传来的 token 和 openID 去请求腾讯的getUserInfo接口,获取腾讯用户的信息,并生成新的 Authtication 对象。<br>


接下来就是要将 QQAuthenticationFilter 与 QQAuthenticationManager 结合,配置到 Spring Security 的过滤器链中。代码如下:




[java]​view plain​​ ​​copy​

​print​​​​?​

  1. @Override  
  2.     protected void configure(HttpSecurity http) throws Exception {  
  3.         http  
  4.                 .authorizeRequests()//authorizeRequests() 定义哪些URL需要被保护、哪些不需要被保护  
  5.                 .antMatchers("/user/**","/news/**","/blog/manage/**").authenticated()  
  6.                 .anyRequest().permitAll()  
  7.                 .and()  
  8.                 .formLogin()  
  9.                 .loginPage("/login")  
  10.                 .successHandler(myAuthenticationSuccessHandler)//登陆成功处理  
  11.                 .permitAll()  
  12.                 .and()  
  13.                 .logout()  
  14.                 .permitAll()  
  15.                 .and().csrf().disable();  
  16.         // 在 UsernamePasswordAuthenticationFilter 前添加 QQAuthenticationFilter  
  17.         http.addFilterAt(qqAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);  
  18.     }  
  19.   
  20.     /**  
  21.      * 自定义 QQ登录 过滤器  
  22.      */  
  23.     private QQAuthenticationFilter qqAuthenticationFilter(){  
  24.         QQAuthenticationFilter authenticationFilter = new QQAuthenticationFilter("/login/qq");  
  25.         //SimpleUrlAuthenticationSuccessHandler successHandler = new SimpleUrlAuthenticationSuccessHandler();  
  26.         //successHandler.setAlwaysUseDefaultTargetUrl(true);  
  27.         //successHandler.setDefaultTargetUrl("/user");  
  28.         MyAuthenticationSuccessHandler successHandler = new MyAuthenticationSuccessHandler();  
  29.         authenticationFilter.setAuthenticationManager(new QQAuthenticationManager());  
  30.         authenticationFilter.setAuthenticationSuccessHandler(successHandler);  
  31.         return authenticationFilter;  
  32.     }  
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()//authorizeRequests() 定义哪些URL需要被保护、哪些不需要被保护
.antMatchers("/user/**","/news/**","/blog/manage/**").authenticated()
.anyRequest().permitAll()
.and()
.formLogin()
.loginPage("/login")
.successHandler(myAuthenticationSuccessHandler)//登陆成功处理
.permitAll()
.and()
.logout()
.permitAll()
.and().csrf().disable();
// 在 UsernamePasswordAuthenticationFilter 前添加 QQAuthenticationFilter
http.addFilterAt(qqAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
/**
* 自定义 QQ登录 过滤器
*/
private QQAuthenticationFilter qqAuthenticationFilter(){
QQAuthenticationFilter authenticationFilter = new QQAuthenticationFilter("/login/qq");
//SimpleUrlAuthenticationSuccessHandler successHandler = new SimpleUrlAuthenticationSuccessHandler();
//successHandler.setAlwaysUseDefaultTargetUrl(true);
//successHandler.setDefaultTargetUrl("/user");
MyAuthenticationSuccessHandler successHandler = new MyAuthenticationSuccessHandler();
authenticationFilter.setAuthenticationManager(new QQAuthenticationManager());
authenticationFilter.setAuthenticationSuccessHandler(successHandler);
return authenticationFilter;
}</pre>说明:由于腾讯的回调地址是 /login/qq,所以 QQAuthenticationFilter 拦截的路径是 /login/qq,然后将 QQAuthenticationFilter 置于 UsernamePasswordAuthenticationFilter 相同级别的位置。<br></div>



前端说明:

前端很简单,一个QQ登陆按钮,代码如下:



[html]​view plain​​ ​​copy​

​print​​​​?​

  1. <a href="https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=101386962&redirect_uri=http://www.ictgu.cn/login/qq&state=test" class="btn btn-primary btn-block">QQ登录</a>  
<a href="https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=101386962&redirect_uri=http://www.ictgu.cn/login/qq&state=test" class="btn btn-primary btn-block">QQ登录</a>

在SpringBoot中对SpringSecurity的基本使用_SpringBoot_02

其他说明:

腾讯官网原话:openid是此网站上唯一对应用户身份的标识,网站可将此ID进行存储便于用户下次登录时辨识其身份,或将其与用户在网站上的原有账号进行绑定。

通过QQ登录获取的 openid 用于与自己网站的账号一一对应。