• 在SpringBoot中对SpringSecurity的基本使用


    参考文献:

    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 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.   
    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}请求,在完成注销之后,页面展现相应的成功消息。

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

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

    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

    后端详解:

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

    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.     }  
  • 相关阅读:
    线程同步的方法
    为什么HashMap中key是引用类型而不是基本数据类型?为什么有了基本数据类型还有包装类型?
    使用MyBatis的mapper接口(动态代理对象)调用时的注意点
    redis的aof持久化模式
    redis的RDB持久化方式的优缺点
    快排算法
    JAVA8新特性
    NIO中Buffer的capacity,position和limit含义
    ArrayBlockingQueue与LinkedBlockingQueue对比
    写加锁但读没有加锁造成的脏读问题
  • 原文地址:https://www.cnblogs.com/jpfss/p/9037473.html
Copyright © 2020-2023  润新知