参考文献:
Spring Security Architecture
What is authentication in Spring Security?
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
基本使用:
添加依赖:
-
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-security</artifactId>
- </dependency>
<!-- 安全框架 Spring Security --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId></dependency>
这里有一篇博客入门学习很不错:Spring boot 中 Spring Security 使用改造5部曲
我的项目中的使用:
自定义的User对象:
-
-
-
-
- public class AnyUser extends 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;
- }
- }
/** * 自定义的 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,那么就相当于有其他所有的权限。
-
-
-
- @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;
- }
-
- }
/** * 自定义 UserDetailsService */@Serviceclass 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; }}
安全控制中心:-
-
-
- @EnableWebSecurity
- 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);
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http
- .authorizeRequests()
- .antMatchers("/user/**","/news/**").authenticated()
- .anyRequest().permitAll()
- .and()
- .formLogin()
- .loginPage("/login")
- .defaultSuccessUrl("/user", true)
- .permitAll()
- .and()
- .logout()
- .permitAll()
- .and().csrf().disable();
- }
-
- }
/** * 安全控制中心 */@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}请求,在完成注销之后,页面展现相应的成功消息。
自定义登录成功处理逻辑:
使登陆成功后跳到登录前页面:
-
- @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);
-
- }
- }
//处理登录成功的。@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个注解,自动注入):
- @Autowired
- private AuthenticationSuccessHandler myAuthenticationSuccessHandler;
@Autowiredprivate AuthenticationSuccessHandler myAuthenticationSuccessHandler;
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http
- .authorizeRequests()
- .antMatchers("/user/**","/news/**","/blog/manage/**","/blog/create/**").authenticated()
- .anyRequest().permitAll()
- .and()
- .formLogin()
- .loginPage("/login")
- .successHandler(myAuthenticationSuccessHandler)
- .permitAll()
- .and()
- .logout()
- .permitAll()
- .and().csrf().disable();
- }
@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登录实现:
准备工作:- 1、在 QQ互联 申请成为开发者,并创建应用,得到APP ID 和 APP Key。
- 2、了解QQ登录时的 网站应用接入流程。(必须看完看懂)。
为了方便各位测试,这里直接提供一个可以使用的:
APP ID:101386962
APP Key:2a0f820407df400b84a854d054be8b6a
提醒:因为回调地址不是 http://localhost ,所以在启动我提供的demo时,需要在host文件中添加一行:127.0.0.1 www.ictgu.cn
后端详解:
1、自定义 QQAuthenticationFilter 继承 AbstractAuthenticationProcessingFilter:
- 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";
-
-
-
-
- private final static String accessTokenUri = "https://graph.qq.com/oauth2.0/token";
-
-
-
-
- private final static String grantType = "authorization_code";
-
-
-
-
- public static final String clientId = "101386962";
-
-
-
-
- private final static String clientSecret = "2a0f820407df400b84a854d054be8b6a";
-
-
-
-
- private final static String redirectUri = "http://www.ictgu.cn/login/qq";
-
-
-
-
- private final static String openIdUri = "https://graph.qq.com/oauth2.0/me?access_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){
-
- 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 {
-
-
-
-
- private String accessToken;
-
-
-
-
- private int expiresIn;
-
-
-
-
- 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;
- }
- }
- }
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,代码如下:
- 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<>();
-
-
-
-
- private final static String userInfoUri = "https://graph.qq.com/user/get_user_info";
-
-
-
-
- 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.setAvatar(json.getString("figureurl_qq_2"));
-
- return user;
- }
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; }
说明:QQAuthenticationManager 的作用是通过传来的 token 和 openID 去请求腾讯的getUserInfo接口,获取腾讯用户的信息,并生成新的 Authtication 对象。
接下来就是要将 QQAuthenticationFilter 与 QQAuthenticationManager 结合,配置到 Spring Security 的过滤器链中。代码如下:
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http
- .authorizeRequests()
- .antMatchers("/user/**","/news/**","/blog/manage/**").authenticated()
- .anyRequest().permitAll()
- .and()
- .formLogin()
- .loginPage("/login")
- .successHandler(myAuthenticationSuccessHandler)
- .permitAll()
- .and()
- .logout()
- .permitAll()
- .and().csrf().disable();
-
- http.addFilterAt(qqAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
- }
-
- /**
- * 自定义 QQ登录 过滤器
- */
- private QQAuthenticationFilter qqAuthenticationFilter(){
- QQAuthenticationFilter authenticationFilter = new QQAuthenticationFilter("/login/qq");
-
-
-
- MyAuthenticationSuccessHandler successHandler = new MyAuthenticationSuccessHandler();
- authenticationFilter.setAuthenticationManager(new QQAuthenticationManager());
- authenticationFilter.setAuthenticationSuccessHandler(successHandler);
- return authenticationFilter;
- }
@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; }
说明:由于腾讯的回调地址是 /login/qq,所以 QQAuthenticationFilter 拦截的路径是 /login/qq,然后将 QQAuthenticationFilter 置于 UsernamePasswordAuthenticationFilter 相同级别的位置。
前端说明:
前端很简单,一个QQ登陆按钮,代码如下:
- <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>
其他说明:
腾讯官网原话:openid是此网站上唯一对应用户身份的标识,网站可将此ID进行存储便于用户下次登录时辨识其身份,或将其与用户在网站上的原有账号进行绑定。
通过QQ登录获取的 openid 用于与自己网站的账号一一对应。