参考文献:

Spring Security Architecture

What is authentication in Spring Security?

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

基本使用:

添加依赖:

  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>

这里有一篇博客入门学习很不错:Spring boot 中 Spring Security 使用改造5部曲

我的项目中的使用:

自定义的User对象:

  1. /**
  2. * 自定义的 User 对象
  3. * 此 User 类不是我们的数据库里的用户类,是用来安全服务的
  4. */
  5. public class AnyUser extends User {
  6. //import org.springframework.security.core.userdetails.User;
  7. private Long id;
  8. private String nickname;
  9. AnyUser(
  10. String username,
  11. String password,
  12. Collection<? extends GrantedAuthority> authorities
  13. ) {
  14. super(username, password, authorities);
  15. }
  16. public Long getId() {
  17. return id;
  18. }
  19. public void setId(Long id) {
  20. this.id = id;
  21. }
  22. public String getNickname() {
  23. return nickname;
  24. }
  25. public void setNickname(String nickname) {
  26. this.nickname = nickname;
  27. }
  28. }
/**
* 自定义的 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&lt;? extends GrantedAuthority&gt; 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,那么就相当于有其他所有的权限。

  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;

    }
}

安全控制中心:

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

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

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

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

  1. @Autowired
  2. private AuthenticationSuccessHandler myAuthenticationSuccessHandler;
@Autowired
private AuthenticationSuccessHandler myAuthenticationSuccessHandler;
  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登录实现:

准备工作:
  • 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:

  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. import javax.servlet.ServletException;
  10. import javax.servlet.http.HttpServletRequest;
  11. import javax.servlet.http.HttpServletResponse;
  12. import java.io.IOException;
  13. import java.util.regex.Matcher;
  14. import java.util.regex.Pattern;
  15. public class QQAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
  16. private final static String CODE = "code";
  17. /**
  18. * 获取 Token 的 API
  19. */
  20. private final static String accessTokenUri = "https://graph.qq.com/oauth2.0/token";
  21. /**
  22. * grant_type 由腾讯提供
  23. */
  24. private final static String grantType = "authorization_code";
  25. /**
  26. * client_id 由腾讯提供
  27. */
  28. public static final String clientId = "101386962";
  29. /**
  30. * client_secret 由腾讯提供
  31. */
  32. private final static String clientSecret = "2a0f820407df400b84a854d054be8b6a";
  33. /**
  34. * redirect_uri 腾讯回调地址
  35. */
  36. private final static String redirectUri = "http://www.ictgu.cn/login/qq";
  37. /**
  38. * 获取 OpenID 的 API 地址
  39. */
  40. private final static String openIdUri = "https://graph.qq.com/oauth2.0/me?access_token=";
  41. /**
  42. * 获取 token 的地址拼接
  43. */
  44. private final static String TOKEN_ACCESS_API = "%s?grant_type=%s&client_id=%s&client_secret=%s&code=%s&redirect_uri=%s";
  45. public QQAuthenticationFilter(String defaultFilterProcessesUrl) {
  46. super(new AntPathRequestMatcher(defaultFilterProcessesUrl, "GET"));
  47. }
  48. @Override
  49. public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
  50. String code = request.getParameter(CODE);
  51. String tokenAccessApi = String.format(TOKEN_ACCESS_API, accessTokenUri, grantType, clientId, clientSecret, code, redirectUri);
  52. QQToken qqToken = this.getToken(tokenAccessApi);
  53. if (qqToken != null){
  54. String openId = getOpenId(qqToken.getAccessToken());
  55. if (openId != null){
  56. // 生成验证 authenticationToken
  57. UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(qqToken.getAccessToken(), openId);
  58. // 返回验证结果
  59. return this.getAuthenticationManager().authenticate(authRequest);
  60. }
  61. }
  62. return null;
  63. }
  64. private QQToken getToken(String tokenAccessApi) throws IOException{
  65. Document document = Jsoup.connect(tokenAccessApi).get();
  66. String tokenResult = document.text();
  67. String[] results = tokenResult.split("&");
  68. if (results.length == 3){
  69. QQToken qqToken = new QQToken();
  70. String accessToken = results[0].replace("access_token=", "");
  71. int expiresIn = Integer.valueOf(results[1].replace("expires_in=", ""));
  72. String refreshToken = results[2].replace("refresh_token=", "");
  73. qqToken.setAccessToken(accessToken);
  74. qqToken.setExpiresIn(expiresIn);
  75. qqToken.setRefresh_token(refreshToken);
  76. return qqToken;
  77. }
  78. return null;
  79. }
  80. private String getOpenId(String accessToken) throws IOException{
  81. String url = openIdUri + accessToken;
  82. Document document = Jsoup.connect(url).get();
  83. String resultText = document.text();
  84. Matcher matcher = Pattern.compile("\"openid\":\"(.*?)\"").matcher(resultText);
  85. if (matcher.find()){
  86. return matcher.group(1);
  87. }
  88. return null;
  89. }
  90. class QQToken {
  91. /**
  92. * token
  93. */
  94. private String accessToken;
  95. /**
  96. * 有效期
  97. */
  98. private int expiresIn;
  99. /**
  100. * 刷新时用的 token
  101. */
  102. private String refresh_token;
  103. String getAccessToken() {
  104. return accessToken;
  105. }
  106. void setAccessToken(String accessToken) {
  107. this.accessToken = accessToken;
  108. }
  109. public int getExpiresIn() {
  110. return expiresIn;
  111. }
  112. void setExpiresIn(int expiresIn) {
  113. this.expiresIn = expiresIn;
  114. }
  115. public String getRefresh_token() {
  116. return refresh_token;
  117. }
  118. void setRefresh_token(String refresh_token) {
  119. this.refresh_token = refresh_token;
  120. }
  121. }
  122. }
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&amp;client_id=%s&amp;client_secret=%s&amp;code=%s&amp;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("&amp;");
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,代码如下:
  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. import static com.zhou.config.qq.QQAuthenticationFilter.clientId;
  17. public class QQAuthenticationManager implements AuthenticationManager {
  18. private static final List<GrantedAuthority> AUTHORITIES = new ArrayList<>();
  19. /**
  20. * 获取 QQ 登录信息的 API 地址
  21. */
  22. private final static String userInfoUri = "https://graph.qq.com/user/get_user_info";
  23. /**
  24. * 获取 QQ 用户信息的地址拼接
  25. */
  26. private final static String USER_INFO_API = "%s?access_token=%s&oauth_consumer_key=%s&openid=%s";
  27. static {
  28. AUTHORITIES.add(new SimpleGrantedAuthority("ROLE_USER"));
  29. }
  30. @Override
  31. public Authentication authenticate(Authentication auth) throws AuthenticationException {
  32. if (auth.getName() != null && auth.getCredentials() != null) {
  33. User user = null;
  34. try {
  35. user = getUserInfo(auth.getName(), (String) (auth.getCredentials()));
  36. } catch (Exception e) {
  37. e.printStackTrace();
  38. }
  39. return new UsernamePasswordAuthenticationToken(user,
  40. null, AUTHORITIES);
  41. }
  42. throw new BadCredentialsException("Bad Credentials");
  43. }
  44. private User getUserInfo(String accessToken, String openId) throws Exception {
  45. String url = String.format(USER_INFO_API, userInfoUri, accessToken, clientId, openId);
  46. Document document;
  47. try {
  48. document = Jsoup.connect(url).get();
  49. } catch (IOException e) {
  50. throw new BadCredentialsException("Bad Credentials!");
  51. }
  52. String resultText = document.text();
  53. JSONObject json = JSON.parseObject(resultText);
  54. User user = new User();
  55. user.setNickname(json.getString("nickname"));
  56. user.setEmail("暂无。。。。");
  57. //user.setGender(json.getString("gender"));
  58. //user.setProvince(json.getString("province"));
  59. //user.setYear(json.getString("year"));
  60. user.setAvatar(json.getString("figureurl_qq_2"));
  61. return user;
  62. }
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&amp;oauth_consumer_key=%s&amp;openid=%s"; static {
AUTHORITIES.add(new SimpleGrantedAuthority("ROLE_USER"));
} @Override
public Authentication authenticate(Authentication auth) throws AuthenticationException {
if (auth.getName() != null &amp;&amp; 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 的过滤器链中。代码如下:

  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. * 自定义 QQ登录 过滤器
  21. */
  22. private QQAuthenticationFilter qqAuthenticationFilter(){
  23. QQAuthenticationFilter authenticationFilter = new QQAuthenticationFilter("/login/qq");
  24. //SimpleUrlAuthenticationSuccessHandler successHandler = new SimpleUrlAuthenticationSuccessHandler();
  25. //successHandler.setAlwaysUseDefaultTargetUrl(true);
  26. //successHandler.setDefaultTargetUrl("/user");
  27. MyAuthenticationSuccessHandler successHandler = new MyAuthenticationSuccessHandler();
  28. authenticationFilter.setAuthenticationManager(new QQAuthenticationManager());
  29. authenticationFilter.setAuthenticationSuccessHandler(successHandler);
  30. return authenticationFilter;
  31. }
@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登陆按钮,代码如下:

  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>


其他说明:

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

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

在SpringBoot中对SpringSecurity的基本使用的更多相关文章

  1. 在SpringBoot中使用SpringSecurity

    @ 目录 提出一个需求 解决方案: 使用SpringSecurity进行解决 SpringSecurity和SpringBoot结合 1. 首先在pom.xml中引入依赖: 2. 配置用户角色和接口的 ...

  2. 记录一下在SpringBoot中实现简单的登录认证

    代码参考博客: https://blog.csdn.net/weixin_37891479/article/details/79527641 在做学校的课设的时候,发现了安全的问题,就不怀好意的用户有 ...

  3. springboot+mybatis+SpringSecurity 实现用户角色数据库管理(一)

    本文使用springboot+mybatis+SpringSecurity 实现用户权限数据库管理 实现用户和角色用数据库存储,而资源(url)和权限的对应采用硬编码配置. 也就是角色可以访问的权限通 ...

  4. SpringBoot中yaml配置对象

    转载请在页首注明作者与出处 一:前言 YAML可以代替传统的xx.properties文件,但是它支持声明map,数组,list,字符串,boolean值,数值,NULL,日期,基本满足开发过程中的所 ...

  5. 如何在SpringBoot中使用JSP ?但强烈不推荐,果断改Themeleaf吧

    做WEB项目,一定都用过JSP这个大牌.Spring MVC里面也可以很方便的将JSP与一个View关联起来,使用还是非常方便的.当你从一个传统的Spring MVC项目转入一个Spring Boot ...

  6. springboot中swaggerUI的使用

    demo地址:demo-swagger-springboot springboot中swaggerUI的使用 1.pom文件中添加swagger依赖 2.从github项目中下载swaggerUI 然 ...

  7. spring-boot+mybatis开发实战:如何在spring-boot中使用myabtis持久层框架

    前言: 本项目基于maven构建,使用mybatis-spring-boot作为spring-boot项目的持久层框架 spring-boot中使用mybatis持久层框架与原spring项目使用方式 ...

  8. 由浅入深学习springboot中使用redis

    很多时候,我们会在springboot中配置redis,但是就那么几个配置就配好了,没办法知道为什么,这里就详细的讲解一下 这里假设已经成功创建了一个springboot项目. redis连接工厂类 ...

  9. Springboot中使用AOP统一处理Web请求日志

    title: Springboot中使用AOP统一处理Web请求日志 date: 2017-04-26 16:30:48 tags: ['Spring Boot','AOP'] categories: ...

随机推荐

  1. PopClip:你会热爱的文本穿梭机

    http://www.ifanr.com/234952 由于我是一名 Evernote 用户(不是印象笔记),最近发现它所提供的浏览器插件无论是 Web Cliper 还是 Clearly,反应速度都 ...

  2. 【转】绝对不要在树莓派上使用无源的HDMI→VGA视频转换器

    http://www.guokr.com/post/521521/ 树莓派由于BCM2835的限制,仅有HDMI和复合视频两种输出形式.所以对于使用VGA显示器的广大用户,HDMI转VGA转换器就成了 ...

  3. javascript 完整知识点整理

    by 蔡舒啸 目录 一 5种基本类型 typeof 关键字 三种强制类型转换 日期 二 if语句for语句whiledo-whileswitch-case 比较运算符 逻辑运算符 if for语句 w ...

  4. Js笔记 14

      <script> // <!-- 课 对象   // //对象的创建方法 // 1.var obj = {} plainobject 对象字面量 对象直接量 // 2.构造函数 ...

  5. vue 使用lib-flexable,px2rem 进行移动端适配 但是引入的第三方UI组件 vux 的样式缩小,解决方案

    最近在写移动端项目,就想用lib-flexable,px2rem来进行适配,把px转换成rem但是也用到了第三方UI组件库vux,把这个引入发现一个问题就是vux的组件都缩小了,在网上找不到答案,最后 ...

  6. python面试题之python多线程与多进程的区别

    多线程可以共享全局变量,多进程不能 多线程中,所有子线程的进程号相同,多进程中,不同的子进程进程号不同 线程共享内存空间:进程的内存是独立的 同一个进程的线程之间可以直接交流:两个进程想通信,必须通过 ...

  7. 转载:使用Pandas进行数据匹配

    使用Pandas进行数据匹配 本文转载自:蓝鲸的网站分析笔记 原文链接:使用Pandas进行数据匹配 目录 merge()介绍 inner模式匹配 lefg模式匹配 right模式匹配 outer模式 ...

  8. Django之FileField字段

    头像上传 在头像上传的时候,属于文件类型 首先视图函数获取的时候,request.FILES.get('文件名变量') avatar_obj = request.FILES.get('avatar') ...

  9. The 2018 ACM-ICPC Chinese Collegiate Programming Contest Caesar Cipher

    #include <iostream> #include <cstdio> #include <cstring> #include <string> # ...

  10. 在终端更改MAC的MySQL的root密码