欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

spring security如何擴(kuò)展自定義登錄

 更新時間:2024年11月14日 14:47:40   作者:小星星1991  
本文詳細(xì)介紹了Spring Security的認(rèn)證原理和具體實(shí)現(xiàn),認(rèn)證原理基于過濾器鏈,通過驗(yàn)證用戶憑證和構(gòu)建認(rèn)證對象來保護(hù)應(yīng)用程序資源,實(shí)現(xiàn)自定義認(rèn)證功能的步驟包括創(chuàng)建自定義認(rèn)證提供程序、實(shí)現(xiàn)UserDetailsService接口以及在配置類中進(jìn)行相應(yīng)的配置

背景

spring security 默認(rèn)給我們提供了一套用戶名密碼登錄的的邏輯實(shí)現(xiàn),在很多時候根本不滿足我們實(shí)際開發(fā)的要求,所以我們經(jīng)常需要對它認(rèn)證模塊進(jìn)行擴(kuò)展,那么如何擴(kuò)展我們的認(rèn)證方式呢,這篇文章將會給你一個較為完整的解答。

認(rèn)證原理

Spring Security使用了一種基于過濾器鏈的認(rèn)證原理來保護(hù)應(yīng)用程序。它提供了一組過濾器,這些過濾器按照特定的順序?qū)γ總€請求進(jìn)行處理。

認(rèn)證過程中的主要步驟如下:

  1. 用戶發(fā)送認(rèn)證請求到應(yīng)用程序。
  2. 應(yīng)用程序?qū)⒄埱蟀l(fā)送給Spring Security的過濾器鏈。
  3. 過濾器鏈依次處理請求,直到找到可以處理該請求的過濾器。
  4. 找到處理請求的過濾器后,該過濾器會驗(yàn)證用戶的憑證(如用戶名和密碼)。
  5. 如果憑證有效,則生成一個已認(rèn)證的安全上下文,并將其保存在Spring Security的上下文存儲中。
  6. 如果憑證無效,則返回認(rèn)證失敗的信息給用戶。
  7. 認(rèn)證成功后,用戶可以繼續(xù)訪問受保護(hù)的資源。

在認(rèn)證過程中,Spring Security還提供了靈活的配置選項,可以根據(jù)特定的需求進(jìn)行自定義配置,例如使用不同的認(rèn)證提供程序、自定義身份驗(yàn)證邏輯等。

總結(jié)起來,Spring Security的認(rèn)證原理是通過一組過濾器鏈來處理認(rèn)證請求,驗(yàn)證用戶憑證并生成認(rèn)證的安全上下文,以保護(hù)應(yīng)用程序的資源。

具體實(shí)現(xiàn)原理

Spring Security的認(rèn)證實(shí)現(xiàn)原理主要涉及以下幾個核心概念和組件:

  • 用戶提供的憑證:用戶在進(jìn)行認(rèn)證時,需要提供其身份憑證,如用戶名和密碼。
  • 認(rèn)證提供程序(Authentication Provider):認(rèn)證提供程序是Spring Security中的重要組件,負(fù)責(zé)驗(yàn)證用戶提供的憑證。它使用用戶提供的憑證與系統(tǒng)中存儲的憑證進(jìn)行比對,并決定是否通過認(rèn)證。Spring
  • Security提供了多種內(nèi)置的認(rèn)證提供程序,如基于數(shù)據(jù)庫的JDBC認(rèn)證提供程序、LDAP認(rèn)證提供程序、InMemory認(rèn)證提供程序等。同時也支持自定義認(rèn)證提供程序。
  • 用戶詳情服務(wù)(UserDetailsService):用戶詳情服務(wù)是Spring Security中的接口,負(fù)責(zé)獲取用戶的詳細(xì)信息,如用戶的權(quán)限、角色等。認(rèn)證提供程序在驗(yàn)證用戶憑證后,會使用用戶詳情服務(wù)獲取用戶的詳細(xì)信息,并構(gòu)建一個認(rèn)證對象(Authentication)。
  • 認(rèn)證對象(Authentication):認(rèn)證對象是Spring Security中表示已認(rèn)證用戶的對象。它包含了用戶的身份信息、憑證信息、權(quán)限信息等。認(rèn)證對象由認(rèn)證提供程序生成,并將其保存在SecurityContextHolder的上下文存儲中。
  • 認(rèn)證過濾器鏈(Authentication Filter Chain):認(rèn)證過濾器鏈?zhǔn)荢pring Security中的過濾器組成的鏈條,負(fù)責(zé)處理認(rèn)證請求。每個過濾器都會對請求進(jìn)行處理,并根據(jù)需要進(jìn)行認(rèn)證、授權(quán)和其他安全操作。在認(rèn)證過程中,認(rèn)證過濾器鏈會根據(jù)請求的URL匹配合適的過濾器進(jìn)行處理。
  • 安全上下文(Security Context):安全上下文是Spring Security中用于保存已認(rèn)證用戶信息的容器。它以ThreadLocal的方式存儲,可以通過SecurityContextHolder來訪問和操作。安全上下文中保存了當(dāng)前的認(rèn)證對象,可以在應(yīng)用程序的任何地方獲取已認(rèn)證用戶的信息。

總結(jié)起來,Spring Security的認(rèn)證實(shí)現(xiàn)原理是通過認(rèn)證提供程序驗(yàn)證用戶提供的憑證,使用用戶詳情服務(wù)獲取用戶的詳細(xì)信息,并構(gòu)建一個認(rèn)證對象,然后將該認(rèn)證對象保存在安全上下文中。

認(rèn)證過濾器鏈負(fù)責(zé)處理認(rèn)證請求,并根據(jù)需要進(jìn)行認(rèn)證、授權(quán)和其他安全操作。

通過這種方式,Spring Security實(shí)現(xiàn)了對應(yīng)用程序進(jìn)行認(rèn)證和授權(quán)的功能。

如何實(shí)現(xiàn)自定義認(rèn)證的功能

要自定義Spring Security的認(rèn)證模塊,你可以按照以下步驟進(jìn)行:

  • 創(chuàng)建一個自定義的認(rèn)證提供程序(Authentication Provider)類,該類實(shí)現(xiàn)了org.springframework.security.authentication.AuthenticationProvider接口。在該類中,你可以編寫你自己的認(rèn)證邏輯,比如驗(yàn)證用戶名和密碼是否匹配。
  • 實(shí)現(xiàn)UserDetailsService接口來獲取用戶的詳細(xì)信息。你可以根據(jù)自己的需求,從數(shù)據(jù)庫、LDAP等數(shù)據(jù)源中獲取用戶信息,并返回一個UserDetails對象。
  • 在配置類(通常是繼承自WebSecurityConfigurerAdapter的類)中,覆蓋configure(AuthenticationManagerBuilder auth)方法。在該方法中,你可以指定使用你自定義的認(rèn)證提供程序和用戶詳情服務(wù)來進(jìn)行認(rèn)證。
  • 在上述配置類中,覆蓋configure(HttpSecurity http)方法來配置認(rèn)證過濾器鏈。你可以根據(jù)需要添加和配置各種過濾器,例如表單登錄過濾器、基于Token的認(rèn)證過濾器等。

下面根據(jù)微信小程序登錄來實(shí)現(xiàn)一個自定義模塊的接口:

定義token信息類

public class JsCodeAuthenticationToken extends AbstractAuthenticationToken {
    private static final long serialVersionUID = 5615450055779559101L;
    private String code;

    /**
     * Creates a token with the supplied array of authorities.
     *
     * @param authorities the collection of <tt>GrantedAuthority</tt>s for the principal
     *                    represented by this authentication object.
     */
    public JsCodeAuthenticationToken(Collection<? extends GrantedAuthority> authorities, String code) {
        super(authorities);
        this.code = code;
    }

    public JsCodeAuthenticationToken(String code) {
        this(Collections.emptyList(), code);
    }

    @Override
    public Object getCredentials() {
        return null;
    }

    @Override
    public Object getPrincipal() {
        return code;
    }

    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
        if (isAuthenticated) {
            throw new IllegalArgumentException(
                    "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
        }

        super.setAuthenticated(false);
    }

    @Override
    public void eraseCredentials() {
        super.eraseCredentials();
    }
}

從代碼中我們可以看出我們需要創(chuàng)建一個相關(guān)的token類來繼承 AbstractAuthenticationToken 類,并實(shí)現(xiàn)相關(guān)的方法

AbstractAuthenticationToken 的定義

  • Spring Security框架中的一個抽象類,用于表示身份驗(yàn)證請求令牌。它用于封裝用戶憑據(jù)或其他必要的信息來進(jìn)行用戶身份驗(yàn)證。
  • AbstractAuthenticationToken的子類負(fù)責(zé)提供特定的身份驗(yàn)證細(xì)節(jié),例如用戶名和密碼、基于令牌的身份驗(yàn)證令牌等。這些子類通常包含獲取器和設(shè)置器,用于訪問和修改身份驗(yàn)證細(xì)節(jié)。
  • AbstractAuthenticationToken還提供了一組方法來管理身份驗(yàn)證狀態(tài),例如設(shè)置已認(rèn)證標(biāo)志、檢索與已認(rèn)證用戶關(guān)聯(lián)的權(quán)限以及管理與身份驗(yàn)證過程相關(guān)的其他詳細(xì)信息。

AbstractAuthenticationToken 的工作原理

  • 當(dāng)用戶請求進(jìn)行身份驗(yàn)證時,Spring Security將創(chuàng)建一個實(shí)現(xiàn)AbstractAuthenticationToken的具體子類對象,并將其中的身份驗(yàn)證信息進(jìn)行填充。然后,該身份驗(yàn)證令牌將被傳遞給身份驗(yàn)證管理器進(jìn)行進(jìn)一步的身份驗(yàn)證處理。
  • 身份驗(yàn)證管理器將檢查身份驗(yàn)證令牌,并根據(jù)所配置的身份驗(yàn)證策略和安全規(guī)則,對用戶進(jìn)行身份驗(yàn)證。在身份驗(yàn)證過程中,身份驗(yàn)證管理器可能會調(diào)用相關(guān)的身份驗(yàn)證提供者,比如用戶名密碼身份驗(yàn)證提供者或令牌身份驗(yàn)證提供者,來驗(yàn)證用戶的憑據(jù)信息。
  • 一旦用戶通過身份驗(yàn)證,身份驗(yàn)證管理器將更新AbstractAuthenticationToken中的已認(rèn)證標(biāo)志和權(quán)限信息,并將其返回給應(yīng)用程序。在后續(xù)的請求中,應(yīng)用程序可以根據(jù)需要訪問身份驗(yàn)證令牌中的身份驗(yàn)證信息和權(quán)限信息,以實(shí)現(xiàn)不同的業(yè)務(wù)邏輯和訪問控制。

定義一個接收參數(shù)的filter

@Slf4j
public class JsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    private static String jsCode = "code";

    private static boolean postOnly = true;

    protected JsCodeAuthenticationFilter(WechatProperties properties) {
        super(new AntPathRequestMatcher(properties.getAuthUrl(), "POST"));
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        if (postOnly && !request.getMethod().equalsIgnoreCase("post")) {
            throw new AuthenticationServiceException("該接口不支持:" + request.getMethod());
        }

        String code = obtainCode(request);
        code = code.trim();
        log.info("得到的code:{}", code);
        JsCodeAuthenticationToken authenticationTokenRequest = new JsCodeAuthenticationToken(code);
        setDetails(request, authenticationTokenRequest);
        return this.getAuthenticationManager().authenticate(authenticationTokenRequest);
    }

    public String obtainCode(HttpServletRequest request) {
        if (request.getHeader("Content-Type").contains("application/json")) {
            return obtainCodeJson(request, jsCode);
        }

        return request.getParameter(jsCode);
    }

    public String obtainCodeJson(HttpServletRequest request, String param) {
        StringBuilder builder = new StringBuilder();
        String line = null;
        String code = null;
        try{
            BufferedReader reader = request.getReader();
            while ((line = reader.readLine()) != null) {
                builder.append(line);
            }

            Map<String, String> map = JsonUtils.fromString(builder.toString(), Map.class, String.class, String.class);
            code = map.get(param);
        }catch (RuntimeException | IOException e) {
            throw new AuthenticationServiceException("獲取參數(shù)失敗");
        }

        return code;
    }

    /**
     * Provided so that subclasses may configure what is put into the
     * authentication request's details property.
     *
     * @param request     that an authentication request is being created for
     * @param authRequest the authentication request object that should have its details
     *                    set
     */
    protected void setDetails(HttpServletRequest request, JsCodeAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }

從上面的代碼可以看出,我們需要定義一個filter 類繼承 AbstractAuthenticationProcessingFilter 來獲取前端傳輸?shù)膮?shù)

AbstractAuthenticationProcessingFilter的定義

  • AbstractAuthenticationProcessingFilter是Spring Security框架中的一個過濾器,用于處理用戶認(rèn)證的相關(guān)操作。

AbstractAuthenticationProcessingFilter的功能有哪些

  • 攔截用戶發(fā)起的認(rèn)證請求,并交給AuthenticationManager來進(jìn)行身份驗(yàn)證。
  • 通過調(diào)用AuthenticationManager完成身份驗(yàn)證后,將認(rèn)證結(jié)果封裝成Authentication對象。
  • 將認(rèn)證結(jié)果傳遞給AbstractAuthenticationProcessingFilter的成功或失敗處理器進(jìn)行處理。
  • 處理認(rèn)證成功或失敗的邏輯,例如生成并返回認(rèn)證成功的Jwt token、跳轉(zhuǎn)到特定頁面、返回錯誤信息等。

定義相關(guān)的Provider 類

public class JsCodeAuthenticationProvider implements AuthenticationProvider {

    private WxOAuth2Service weChatService;

    private UserDetailsService userDetailsService;

    private WxMpProperties wxMpProperties;

    public JsCodeAuthenticationProvider(WxOAuth2Service weChatService, WxMpProperties wxMpProperties) {
        this.weChatService = weChatService;
        this.wxMpProperties = wxMpProperties;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        JsCodeAuthenticationToken token = (JsCodeAuthenticationToken) authentication;
        String code = (String) token.getPrincipal();
        log.info("得到的code:{}", code);
        if (StringUtils.isBlank(code)) {
            throw new AuthenticationServiceException("jscode 不能為空");
        }

        WxOAuth2AccessToken wxOAuth2AccessToken;
        try {
            wxOAuth2AccessToken = weChatService.getAccessToken(wxMpProperties.getAppId(), wxMpProperties.getSecret(), code);
        } catch (WxErrorException e) {
            throw new AuthenticationServiceException("授權(quán)登錄失敗");
        }



        WeChatDetailsService weChatDetailsService = (WeChatDetailsService)userDetailsService;
        UserDetails details = weChatDetailsService.loadByToken(wxOAuth2AccessToken.getOpenId(), wxOAuth2AccessToken.getAccessToken());
        if (details == null) {
            throw new AuthenticationServiceException("無法獲取公眾號用戶");
        }

        JsCodeAuthenticationToken authenticationResult = new JsCodeAuthenticationToken(details.getUsername());
        authenticationResult.setDetails(token.getDetails());
        return authenticationResult;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return JsCodeAuthenticationToken.class.isAssignableFrom(authentication);
    }

    public void setUserDetailsService(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }
}

AuthenticationProvider 的定義

AuthenticationProvider是Spring Security框架中的一個接口,用于身份驗(yàn)證的核心組件。

它的定義如下:

public interface AuthenticationProvider {
 Authentication authenticate(Authentication authentication) throws AuthenticationException;
 boolean supports(Class<?> authentication);
}

它包含兩個方法:

  • authenticate(Authentication authentication):該方法用于執(zhí)行身份驗(yàn)證過程。輸入?yún)?shù)authentication是一個封裝了用戶認(rèn)證信息的Authentication對象,包括用戶名、密碼等信息。該方法返回一個認(rèn)證成功的Authentication對象,或者拋出AuthenticationException異常表示認(rèn)證失敗。
  • supports(Class<?> authentication):該方法用于判斷是否支持給定類型的認(rèn)證請求。輸入?yún)?shù)authentication是要被驗(yàn)證的對象類型,通常是UsernamePasswordAuthenticationToken或JwtAuthenticationToken等。該方法返回一個boolean值,表示是否支持該類型的認(rèn)證請求。

AuthenticationProvider可以自定義實(shí)現(xiàn),用于實(shí)現(xiàn)不同的身份驗(yàn)證邏輯。開發(fā)者可以通過實(shí)現(xiàn)該接口,并覆蓋其中的方法來定制化身份驗(yàn)證過程,例如從數(shù)據(jù)庫查詢用戶信息并進(jìn)行密碼校驗(yàn)等。

在Spring Security的配置中,可以通過AuthenticationManagerBuilder來注冊和配置AuthenticationProvider的實(shí)例,以供身份驗(yàn)證使用。

定義相關(guān)配置類

@Component
@EnableConfigurationProperties(WxMpProperties.class)
public class JsCodeAuthenticationSecurityConfig  extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private AuthenticationSuccessHandler jsCodeAuthenticationSuccessHandler;

    @Autowired
    private AuthenticationFailureHandler jsCodeAuthenticationFailureHandler;

    @Autowired
    private WxOAuth2Service weChatService;

    @Autowired
    private WechatProperties weChatProperties;

    @Autowired
    private WxMpProperties wxMpProperties;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        JsCodeAuthenticationFilter mobilePasswordAuthenticationFilter = new JsCodeAuthenticationFilter(weChatProperties);
        mobilePasswordAuthenticationFilter.setAuthenticationSuccessHandler(jsCodeAuthenticationSuccessHandler);
        mobilePasswordAuthenticationFilter.setAuthenticationFailureHandler(jsCodeAuthenticationFailureHandler);
        mobilePasswordAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
        JsCodeAuthenticationProvider provider = new JsCodeAuthenticationProvider(weChatService, wxMpProperties);
        provider.setUserDetailsService(userDetailsService);
        // 將當(dāng)前服務(wù)注冊到 mobilePasswordAuthenticationFilter 連之后
        http.authenticationProvider(provider).addFilterAfter(mobilePasswordAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

SecurityConfigurerAdapter 的定義

SecurityConfigurerAdapter是Spring Security提供的一個抽象類,用于簡化配置和定制化Spring Security的行為。

它是一個適配器模式,繼承自SecurityConfigurer接口,并提供了一些默認(rèn)實(shí)現(xiàn)方法,用于提供給開發(fā)者靈活地配置和擴(kuò)展Spring Security。

SecurityConfigurerAdapter的定義如下:

public abstract class SecurityConfigurerAdapter<O extends SecurityFilterChain, B extends SecurityBuilder<O>> implements SecurityConfigurer<O, B> {
 // 提供一個空實(shí)現(xiàn)的configure方法,供子類進(jìn)行覆蓋定制
  public void configure(B builder) throws Exception {}
 // 提供一個空實(shí)現(xiàn)的init方法,供子類進(jìn)行覆蓋定制 
 public void init(B builder) throws Exception {}
 // 提供一個空實(shí)現(xiàn)的configure方法,供子類進(jìn)行覆蓋定制 
 public void configure(O object) throws Exception {}
 // 提供一個空實(shí)現(xiàn)的postProcess方法,供子類進(jìn)行覆蓋定制 
 protected void postProcess(O object) throws Exception {}
}

開發(fā)者可以繼承SecurityConfigurerAdapter,并重寫其中的方法來實(shí)現(xiàn)自定義的安全配置。常見的用法是在WebSecurityConfigurerAdapter中繼承SecurityConfigurerAdapter,并重寫configure方法來配置Spring Security的行為,例如定義認(rèn)證規(guī)則、權(quán)限控制等。通過繼承SecurityConfigurerAdapter可以避免直接實(shí)現(xiàn)SecurityConfigurer接口時需要實(shí)現(xiàn)所有方法的繁瑣操作。

整體配置

    public void configure(HttpSecurity http) throws Exception {
        String[] auth = StringUtils.split( securityProperties.getAuthUrl(), ",");
        http.formLogin().failureHandler(authenticationFailureHandler)
                .successHandler(authenticationSuccessHandler)
                .loginProcessingUrl(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_FORM)
                .loginPage(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL)
                .and().apply(jsCodeAuthenticationSecurityConfig)
                .and().apply(usernameAuthenticationSecurityConfig())
                .and().authorizeRequests().antMatchers(auth).authenticated()
                .and().authorizeRequests().anyRequest().permitAll()
                .and().csrf().disable();
    }

總結(jié)

以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • 常用的ResponseEntity.BodyBuilder和自定義ResponseEntity的實(shí)例

    常用的ResponseEntity.BodyBuilder和自定義ResponseEntity的實(shí)例

    這篇文章主要介紹了常用的ResponseEntity.BodyBuilder和自定義ResponseEntity的實(shí)例,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-07-07
  • java壓縮文件與刪除文件的示例代碼

    java壓縮文件與刪除文件的示例代碼

    這篇文章主要介紹了java壓縮文件與刪除文件的示例代碼,代碼簡單易懂,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-08-08
  • 深入了解SpringBoot中@ControllerAdvice的介紹及三種用法

    深入了解SpringBoot中@ControllerAdvice的介紹及三種用法

    這篇文章主要為大家詳細(xì)介紹了SpringBoot中@ControllerAdvice的介紹及三種用法,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-02-02
  • Mybatis的TypeHandler加解密數(shù)據(jù)實(shí)現(xiàn)

    Mybatis的TypeHandler加解密數(shù)據(jù)實(shí)現(xiàn)

    在我們數(shù)據(jù)庫中有些時候會保存一些用戶的敏感信息,所以就需要對這些數(shù)據(jù)進(jìn)行加密,那么本文就介紹了Mybatis的TypeHandler加解密數(shù)據(jù)實(shí)現(xiàn),感興趣的可以了解一下
    2021-06-06
  • java出現(xiàn)no XXX in java.library.path的解決及eclipse配置方式

    java出現(xiàn)no XXX in java.library.path的解決及eclipse配

    這篇文章主要介紹了java出現(xiàn)no XXX in java.library.path的解決及eclipse配置方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • java靜態(tài)工具類注入service出現(xiàn)NullPointerException異常處理

    java靜態(tài)工具類注入service出現(xiàn)NullPointerException異常處理

    如果我們要在我們自己封裝的Utils工具類中或者非controller普通類中使用@Autowired注解注入Service或者M(jìn)apper接口,直接注入是報錯的,因Utils用了靜態(tài)方法,我們無法直接用非靜態(tài)接口的,遇到這問題,我們要想法解決,下面小編就簡單介紹解決辦法,需要的朋友可參考下
    2021-09-09
  • 解決Mybatis-Plus操作分頁后數(shù)據(jù)失效問題

    解決Mybatis-Plus操作分頁后數(shù)據(jù)失效問題

    這篇文章主要介紹了解決Mybatis-Plus操作分頁后數(shù)據(jù)失效問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-11-11
  • 最新評論