在SpringBoot中對SpringSecurity的基本使用

參考文獻:html

Spring Security Architecture
前端

What is authentication in Spring Security?
java

Spring Security是一個可以爲基於Spring的企業應用系統提供聲明式的安全訪問控制解決方案的安全框架。它提供了一組能夠在Spring應用上下文中配置的Bean,爲應用系統提供聲明式的安全訪問控制功能,減小了爲企業系統安全控制編寫大量重複代碼的工做。node

基本使用:

添加依賴:
web

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

這裏有一篇博客入門學習很不錯:Spring boot 中 Spring Security 使用改造5部曲spring

個人項目中的使用:

自定義的User對象:數據庫

  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. }  
繼承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. }  
安全控制中心:

  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. }  

Spring Security提供了一個過濾器來攔截請求並驗證用戶身份。若是用戶身份認證失敗,頁面就重定向到/login?error,而且頁面中會展示相應的錯誤信息。若用戶想要註銷登陸,能夠經過訪問@{/logout}請求,在完成註銷以後,頁面展示相應的成功消息。json

自定義登陸成功處理邏輯:
後端

使登錄成功後跳到登陸前頁面:安全

  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. }  

從新配置安全中心(代碼完成以後,修改配置config類代碼。添加2個註解,自動注入):

  1. @Autowired  
  2. 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.     }  

QQ登陸實現:

準備工做:
爲了方便各位測試,這裏直接提供一個可使用的:
APP ID:101386962
APP Key:2a0f820407df400b84a854d054be8b6a

提醒:由於回調地址不是 http://localhost ,因此在啓動我提供的demo時,須要在host文件中添加一行:127.0.0.1 www.ictgu.cn

後端詳解:

一、自定義 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.   
  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. }  
說明: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.   
  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.     }  
說明:QQAuthenticationManager 的做用是經過傳來的 token 和 openID 去請求騰訊的getUserInfo接口,獲取騰訊用戶的信息,並生成新的 Authtication 對象。
接下來就是要將 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.     /**  
  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.     }  
說明:因爲騰訊的回調地址是 /login/qq,因此 QQAuthenticationFilter 攔截的路徑是 /login/qq,而後將 QQAuthenticationFilter 置於 UsernamePasswordAuthenticationFilter 相同級別的位置。

前端說明:

前端很簡單,一個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>  
其餘說明: 騰訊官網原話:openid是此網站上惟一對應用戶身份的標識,網站可將此ID進行存儲便於用戶下次登陸時辨識其身份,或將其與用戶在網站上的原有帳號進行綁定。 經過QQ登陸獲取的 openid 用於與本身網站的帳號一一對應。
相關文章
相關標籤/搜索