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

SpringBoot集成Spring Security的方法

 更新時(shí)間:2020年07月06日 09:43:10   作者:良許Linux  
Spring security,是一個(gè)強(qiáng)大的和高度可定制的身份驗(yàn)證和訪問控制框架。這篇文章主要介紹了SpringBoot集成Spring Security的操作方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

至今Java能夠如此的火爆Spring做出了很大的貢獻(xiàn),它的出現(xiàn)讓Java程序的編寫更為簡單靈活,而Spring如今也形成了自己的生態(tài)圈,今天咱們探討的是Spring旗下的一個(gè)款認(rèn)證工具:SpringSecurity,如今認(rèn)證框架主流“shiro”和“SpringSecurity”,由于和Spring的無縫銜接,使用SpringSecurity的企業(yè)也越來越多。

1、Spring Security介紹

Spring security,是一個(gè)強(qiáng)大的和高度可定制的身份驗(yàn)證和訪問控制框架。它是確?;赟pring的應(yīng)用程序的標(biāo)準(zhǔn)——來自官方參考手冊

Spring security 和 shiro 一樣,具有認(rèn)證、授權(quán)、加密等用于權(quán)限管理的功能。和 shiro 不同的是,Spring security擁有比shiro更豐富的功能,并且,對于Springboot而言,Spring Security比Shiro更合適一些,因?yàn)槎际荢pring家族成員。今天,我們來為SpringBoot項(xiàng)目集成Spring Security。

本文所使用的版本:

​SpringBoot : 2.2.6.RELEASE
​Spring Security : 5.2.2.RELEASE

2、配置Spring Security

在SpringBoot中集成Spring Security很簡單,只需要在pom.xml中添加下面代碼就行:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>

這里可以不指定Spring Security的版本號(hào),它會(huì)根據(jù)SpringBoot的版本來匹配對應(yīng)的版本,SpringBoot版本是 2.2.6.RELEASE,對應(yīng)Spring Security的版本是5.2.2.RELEASE。

然后,我們就可以將springboot啟動(dòng)了。

當(dāng)我們嘗試訪問項(xiàng)目時(shí),它會(huì)跳轉(zhuǎn)到這個(gè)界面來:

​對!在此之前,你什么也不用做。這就是Spring Security的優(yōu)雅之處。你只需要引入Spring Security的包,它就能在你的項(xiàng)目中工作。因?yàn)樗呀?jīng)幫你實(shí)現(xiàn)了一個(gè)簡單的登陸界面。根據(jù)官方介紹,登錄使用的賬號(hào)是user,密碼是隨機(jī)密碼,這個(gè)隨機(jī)密碼可以在控制臺(tái)中找到,類似這樣的一句話:

Using generated security password: 1cb77bc5-8d74-4846-9b6c-4813389ce096

​Using generated security password后面的的就是系統(tǒng)給的隨機(jī)密碼,我們可以使用這個(gè)密碼進(jìn)行登錄。隨機(jī)密碼在每一次啟動(dòng)服務(wù)后生成(如果你配置了熱部署devtools,你得隨時(shí)留意控制臺(tái)了,因?yàn)槊慨?dāng)你修改了代碼,系統(tǒng)會(huì)自動(dòng)重啟,那時(shí)隨機(jī)密碼就會(huì)重新生成)。

​當(dāng)然,這樣的功能一定不是你想要的,也一定不會(huì)就這樣拿給你的用戶使用。那么,接下來,讓我們把它配置成我們想要的樣子。

​要實(shí)現(xiàn)自定義配置,首先要?jiǎng)?chuàng)建一個(gè)繼承于WebSecurityConfigurerAdapter的配置類:

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

}

​這里使用了@EnableWebSecurity注解,這個(gè)注解是Spring Security用于啟用web安全的注解。具體實(shí)現(xiàn),這里就不深入了。

​要實(shí)現(xiàn)自定義攔截配置,首先得告訴Spring Security,用戶信息從哪里獲取,以及用戶對應(yīng)的角色等信息。這里就需要重寫WebSecurityConfigurerAdapter的configure(AuthenticationManagerBuilder auth)方法了。這個(gè)方法將指使Spring Security去找到用戶列表,然后再與想要通過攔截器的用戶進(jìn)行比對,再進(jìn)行下面的步驟。

​Spring Security的用戶存儲(chǔ)配置有多個(gè)方案可以選擇,包括:

  • 內(nèi)存用戶存儲(chǔ)
  • 數(shù)據(jù)庫用戶存儲(chǔ)
  • LDAP用戶存儲(chǔ)
  • 自定義用戶存儲(chǔ)

​我們分別來看看這幾種用戶存儲(chǔ)的配置方法:

1.內(nèi)存用戶存儲(chǔ)

​此配置方式是直接將用戶信息存儲(chǔ)在內(nèi)存中,這種方式在速度上無疑是最快的。但只適用于有限個(gè)用戶數(shù)量,且這些用戶幾乎不會(huì)發(fā)生改變。我們來看看配置方法:

@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.inMemoryAuthentication().passwordEncoder(passwordEncoder())
			.withUser("zhangsan").password(passwordEncoder().encode("123456")).authorities("ADMIN")
			.and()
			.withUser("lisi").password(passwordEncoder().encode("123456")).authorities("ORDINARY");
	}
	
	private PasswordEncoder passwordEncoder() {
  return new BCryptPasswordEncoder();
 }

​可以看到,AuthenticationManagerBuilder使用構(gòu)造者方式來構(gòu)建的。在上面方法中,先調(diào)用了inMemoryAuthentication()方法,它來指定用戶存儲(chǔ)在內(nèi)存中。接下來又調(diào)用了passwordEncoder()方法,這個(gè)方法的作用是告訴Spring Security認(rèn)證密碼的加密方式。因?yàn)樵赟pring security5過后,必須指定某種加密方式,不然程序會(huì)報(bào)錯(cuò)。接下來調(diào)用的withUser()、password()、authorities()方法,分別是在指定用戶的賬號(hào)、密碼以及權(quán)限名。在添加完一個(gè)用戶后,要使用and()方法來連接下一個(gè)用戶的添加。

​如果使用這種配置方法,你會(huì)發(fā)現(xiàn),在修改用戶時(shí),就必須修改代碼。對于絕大多數(shù)項(xiàng)目來說,這種方式是滿足不了需求的,至少我們需要一個(gè)注冊功能。

2.數(shù)據(jù)庫用戶存儲(chǔ)

​將用戶信息存儲(chǔ)在數(shù)據(jù)庫中,讓我們可以很方便地對用戶信息進(jìn)行增刪改查。并且還可以為用戶添加除認(rèn)證信息外的附加信息,這樣的設(shè)計(jì)也是我們很多小心應(yīng)用所采取的方式。讓我們來實(shí)現(xiàn)以下:

@Autowired
	private DataSource dataSource;
	
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.jdbcAuthentication().dataSource(dataSource).passwordEncoder(passwordEncoder())
			.usersByUsernameQuery(
					"select username, password, status from Users where username = ?")
			.authoritiesByUsernameQuery(
					"select username, authority from Authority where username = ?");
		
	}
	
	private PasswordEncoder passwordEncoder() {
  return new BCryptPasswordEncoder();
 }

​調(diào)用jdbcAuthentication()來告訴Spring Security使用jdbc的方式來查詢用戶和權(quán)限,dataSource()方法指定數(shù)據(jù)庫連接信息,passwordEncoder()指定密碼加密規(guī)則,用戶的密碼數(shù)據(jù)應(yīng)該以同樣的方式進(jìn)行加密存儲(chǔ),不然,兩個(gè)加密方式不同的密碼,匹配補(bǔ)上。usersByUsernameQuery()和authoritiesByUsernameQuery()方法分別定義了查詢用戶和權(quán)限信息的sql語句。其實(shí),Spring security為我們默認(rèn)了查詢用戶、權(quán)限甚至還有群組用戶授權(quán)的sql,這三條默認(rèn)的sql存放在org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl中,有興趣的小伙伴可以點(diǎn)進(jìn)去看看。如果你要使用默認(rèn)的,那你的表中關(guān)鍵性的字段必須和語句中的一致。

​使用數(shù)據(jù)庫來存儲(chǔ)用戶和權(quán)限等信息已經(jīng)可以滿足大部分的需求。但是Spring security還為我們提供了另外一種配置方式,讓我們來看一下。

3.LDAP用戶存儲(chǔ)

​LDAP:輕型目錄訪問協(xié)議,是一個(gè)開放的,中立的,工業(yè)標(biāo)準(zhǔn)的應(yīng)用協(xié)議,通過IP協(xié)議提供訪問控制和維護(hù)分布式信息的目錄信息。簡單來說,就是將用戶信息存放在另外一臺(tái)服務(wù)器中(當(dāng)然,也可以在同一臺(tái)服務(wù)器,但我們一般不這么做),通過網(wǎng)絡(luò)來進(jìn)行訪問的技術(shù)。

​我們來簡單配置一下:

@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		LdapAuthenticationProviderConfigurer<AuthenticationManagerBuilder> configurer = 					auth.ldapAuthentication()
			.userSearchBase("ou=people")
			.userSearchFilter("(uid={0})")
			.groupSearchBase("ou=groups")
			.groupSearchFilter("member={0}");
			
		configurer.passwordCompare()
			.passwordEncoder(passwordEncoder())
			.passwordAttribute("passcode");
		configurer.contextSource().url("ldap://xxxxx.com:33389/dc=xxxxxx,dc=com");
	}
	
	private PasswordEncoder passwordEncoder() {
  return new BCryptPasswordEncoder();
 }

​userSearchFilter()和groupSearchFilter()設(shè)置的是用戶和群組的過濾條件,而userSearchBase()和groupSearchBase()設(shè)置了搜索起始位置,contextSource().url()設(shè)置LDAP服務(wù)器的地址。如果沒有遠(yuǎn)程的服務(wù)器可以使用contextSource().root()來使用嵌入式LDAP服務(wù)器,此方式將使用項(xiàng)目中的用戶數(shù)據(jù)文件來提供認(rèn)證服務(wù)。

​如果以上幾種方式還不能滿足我們的需求,我們可以用自定義的方式來配置。

4.自定義用戶存儲(chǔ)

​自定義用戶存儲(chǔ),就是自行使用認(rèn)證名稱來查找對應(yīng)的用戶數(shù)據(jù),然后交給Spring Security使用。我們需要定義一個(gè)實(shí)現(xiàn)UserDetailsService的service類:

@Service
public class MyUserDetailsService implements UserDetailsService{

	@Autowired
	private UserMapper userMapper;
	
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		User user = userMapper.getUserByUsername(username);
		return user == null ? new User() : user;
	}
}

public class User implements UserDetails {
 ...
}

​該類只需要實(shí)現(xiàn)一個(gè)方法:loadUserByUsername()。該方法需要做的是使用傳過來的username來匹配一個(gè)帶有密碼等信息的用戶實(shí)體。需要注意的是這里的User類需要實(shí)現(xiàn)UserDetails,也就是說,查到的信息里,必須得有Spring Security所需要的信息。

​下面,讓我們來繼續(xù)配置:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	private MyUserDetailsService userDetailsService;
	
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.userDetailsService(userDetailsService)
			.passwordEncoder(passwordEncoder());
	}
	
	@Bean
	private PasswordEncoder passwordEncoder() {
  return new BCryptPasswordEncoder();
 }
}

​這樣的配置方法就很簡單了,只需要告訴Spring Security你的UserDetailsService實(shí)現(xiàn)類是哪個(gè)就可以了,它會(huì)去調(diào)用loadUserByUsername()來查找用戶。

​以上就是Spring Security所提供的4種用戶存儲(chǔ)方式,接下來,需要考慮的是,怎么攔截請求。

3、請求攔截

1.安全規(guī)則

​Spring Security的請求攔截配置方法是用戶存儲(chǔ)配置方法的重載方法,我們先來簡單配置一下:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests()
			.antMatchers("/user", "/menu")
			.hasRole("ADMIN")
			.antMatchers("/", "/**").permitAll();
	}
}

​調(diào)用authorizeRequests()方法后,就可以添加自定義攔截路徑了。antMatchers()方法配置了請求路徑,hasRole()和permitAll()指定了訪問規(guī)則,分別表示擁有“ADMIN”權(quán)限的用戶才能訪問、所有用戶可以訪問。

​需要注意的是:這里的配置需要成對出現(xiàn),并且配置的順序也很重要。聲明在前面的規(guī)則擁有更高的優(yōu)先級(jí)。也就是說,如果我們將.antMatchers("/", "/").permitAll()**放到了最前面,像這樣:

@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests()
			.antMatchers("/", "/**").permitAll()
    .antMatchers("/user", "/menu")
			.hasRole("ADMIN");
	}

​那么,下面的"/user"和 "/menu"的配置是徒勞,因?yàn)榍懊娴囊?guī)則已經(jīng)指明所有路徑能被所有人訪問。當(dāng)然權(quán)限的規(guī)則方法還有很多,我這里只列舉了兩個(gè)。以下為常見的內(nèi)置表達(dá)式:

表達(dá) 描述
hasRole(String role) 返回true當(dāng)前委托人是否具有指定角色。例如, hasRole('admin')默認(rèn)情況下,如果提供的角色不是以“ ROLE_”開頭,則會(huì)添加該角色。可以通過修改defaultRolePrefixon來自定義DefaultWebSecurityExpressionHandler。
hasAnyRole(String… roles) 返回true當(dāng)前委托人是否具有提供的任何角色(以逗號(hào)分隔的字符串列表形式)。例如, hasAnyRole('admin', 'user')默認(rèn)情況下,如果提供的角色不是以“ ROLE_”開頭,則會(huì)添加該角色??梢酝ㄟ^修改defaultRolePrefixon來自定義DefaultWebSecurityExpressionHandler。
hasAuthority(String authority) 返回true當(dāng)前委托人是否具有指定權(quán)限。例如, hasAuthority('read')
hasAnyAuthority(String… authorities) 返回true如果當(dāng)前主體具有任何所提供的當(dāng)局的(給定為逗號(hào)分隔的字符串列表)例如, hasAnyAuthority('read', 'write')
principal 允許直接訪問代表當(dāng)前用戶的主體對象
authentication 允許直接訪問Authentication從SecurityContext
permitAll 始終評(píng)估為 true
denyAll 始終評(píng)估為 false
isAnonymous() 返回true當(dāng)前委托人是否為匿名用戶
isRememberMe() 返回true當(dāng)前主體是否是“記住我”的用戶
isAuthenticated() true如果用戶不是匿名的,則返回
isFullyAuthenticated() 返回true如果用戶不是匿名或記得,我的用戶
hasPermission(Object target, Object permission) 返回true用戶是否可以訪問給定權(quán)限的給定目標(biāo)。例如,hasPermission(domainObject, 'read')
hasPermission(Object targetId, String targetType, Object permission) 返回true用戶是否可以訪問給定權(quán)限的給定目標(biāo)。例如,hasPermission(1, 'com.example.domain.Message', 'read')

除此之外,還有一個(gè)支持SpEL表達(dá)式計(jì)算的方法,它的使用方法如下:

@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests()
			.antMatchers("/user", "/menu")
			.access("hasRole('ADMIN')")
			.antMatchers("/", "/**").permitAll();
	}

​它所實(shí)現(xiàn)的規(guī)則和上面的方法一樣。Spring Security還提供了其他豐富的SpEL表達(dá)式,如:

表達(dá) 描述
hasRole(String role) 返回true當(dāng)前委托人是否具有指定角色。例如, hasRole('admin')默認(rèn)情況下,如果提供的角色不是以“ ROLE_”開頭,則會(huì)添加該角色??梢酝ㄟ^修改defaultRolePrefixon來自定義DefaultWebSecurityExpressionHandler。
hasAnyRole(String… roles) 返回true當(dāng)前委托人是否具有提供的任何角色(以逗號(hào)分隔的字符串列表形式)。例如, hasAnyRole('admin', 'user')默認(rèn)情況下,如果提供的角色不是以“ ROLE_”開頭,則會(huì)添加該角色??梢酝ㄟ^修改defaultRolePrefixon來自定義DefaultWebSecurityExpressionHandler。
hasAuthority(String authority) 返回true當(dāng)前委托人是否具有指定權(quán)限。例如, hasAuthority('read')
hasAnyAuthority(String… authorities) 返回true如果當(dāng)前主體具有任何所提供的當(dāng)局的(給定為逗號(hào)分隔的字符串列表)例如, hasAnyAuthority('read', 'write')
principal 允許直接訪問代表當(dāng)前用戶的主體對象
authentication 允許直接訪問Authentication從SecurityContext
permitAll 始終評(píng)估為 true
denyAll 始終評(píng)估為 false
isAnonymous() 返回true當(dāng)前委托人是否為匿名用戶
isRememberMe() 返回true當(dāng)前主體是否是“記住我”的用戶
isAuthenticated() true如果用戶不是匿名的,則返回
isFullyAuthenticated() 返回true如果用戶不是匿名或記得,我的用戶
hasPermission(Object target, Object permission) 返回true用戶是否可以訪問給定權(quán)限的給定目標(biāo)。例如,hasPermission(domainObject, 'read')
hasPermission(Object targetId, String targetType, Object permission) 返回true用戶是否可以訪問給定權(quán)限的給定目標(biāo)。例如,hasPermission(1, 'com.example.domain.Message', 'read')

2.登錄

​如果此時(shí),我們有自己的登錄界面,需要替換掉Spring Security所提供的默認(rèn)的界面,這時(shí)可以用fromLogin()和loginPage()方法來實(shí)現(xiàn):

@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests()
			.antMatchers("/user", "/menu")
			.access("hasRole('ADMIN')")
			.antMatchers("/", "/**").permitAll()
			.and()
			.formLogin()
			.loginPage("/login");
	}

​這便將登錄地址指向了“/login”。如果需要指定登錄成功時(shí),跳轉(zhuǎn)的地址,可以使用defaultSuccessUrl()方法:

 .and()
   .formLogin()
   .loginPage("/login")
   .defaultSuccessUrl("/home")

​此時(shí)用戶登錄過后,將跳轉(zhuǎn)到主頁來。

​下面,我們來看看登出。

3.登出

​和登錄類似的,可以使用logout()和logoutSuccessUrl()方法來實(shí)現(xiàn):

.and()
			.logout()
			.logoutSuccessUrl("/login")

​上面例子中,用戶登出后將跳轉(zhuǎn)到登錄界面。

4、小結(jié)

​至此,我們已基本了解了Spring Security配置,可以將它配置成我們想要的樣子(基本)。其實(shí)Spring Security能做的事還有很多,光看我這篇文章是不夠的。學(xué)習(xí)它最有效的方法就是閱讀官方文檔。里面有關(guān)于Spring Security最全最新的知識(shí)!(官網(wǎng)地址:https://spring.io/projects/spring-security)

到此這篇關(guān)于SpringBoot集成Spring Security的文章就介紹到這了,更多相關(guān)SpringBoot集成Spring Security內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java中參數(shù)傳遞方式詳解

    java中參數(shù)傳遞方式詳解

    這篇文章主要介紹了java中參數(shù)傳遞方式詳解的相關(guān)資料,需要的朋友可以參考下
    2017-03-03
  • 使用Java WebSocket獲取客戶端IP地址的示例代碼

    使用Java WebSocket獲取客戶端IP地址的示例代碼

    在開發(fā)Web應(yīng)用程序時(shí),我們通常需要獲取客戶端的 IP 地址用于日志記錄、身份驗(yàn)證、限制訪問等操作,本文將介紹如何使用Java WebSocket API獲取客戶端IP地址,以及如何在常見的WebSocket框架中獲得客戶端 IP地址,需要的朋友可以參考下
    2023-11-11
  • ChatGpt都使用的Java BPE分詞算法不要了解一下

    ChatGpt都使用的Java BPE分詞算法不要了解一下

    Byte Pair Encoding(BPE)是一種文本壓縮算法,它通常用于自然語言處理領(lǐng)域中的分詞、詞匯表構(gòu)建等任務(wù),本文將對 BPE 算法進(jìn)行全面、詳細(xì)的講解,并提供 Java 相關(guān)的代碼示例,希望對大家有所幫助
    2023-06-06
  • 詳解hibernate4基本實(shí)現(xiàn)原理

    詳解hibernate4基本實(shí)現(xiàn)原理

    本文通過圖文并茂的形式給大家介紹的hibernate4基本實(shí)現(xiàn)原理,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧
    2017-09-09
  • 淺談Spring如何解決循環(huán)依賴的問題

    淺談Spring如何解決循環(huán)依賴的問題

    這篇文章主要介紹了淺談Spring如何解決循環(huán)依賴的問題,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09
  • 利用Maven添加工程版本信息及時(shí)間戳

    利用Maven添加工程版本信息及時(shí)間戳

    這篇文章主要介紹了利用Maven添加工程版本信息及時(shí)間戳方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • MyBatis一級(jí)與二級(jí)緩存相關(guān)配置

    MyBatis一級(jí)與二級(jí)緩存相關(guān)配置

    mybatis-plus是一個(gè)Mybatis的增強(qiáng)工具,在Mybatis的基礎(chǔ)上只做增強(qiáng)不做改變,為簡化開發(fā)、提高效率而生,這篇文章帶你了解Mybatis的一級(jí)和二級(jí)緩存
    2023-01-01
  • Java通過SSLEngine與NIO實(shí)現(xiàn)HTTPS訪問的操作方法

    Java通過SSLEngine與NIO實(shí)現(xiàn)HTTPS訪問的操作方法

    這篇文章主要介紹了Java通過SSLEngine與NIO實(shí)現(xiàn)HTTPS訪問,需要在Connect操作、Connected操作、Read和Write操作中加入SSL相關(guān)的處理即可,需要的朋友可以參考下
    2021-08-08
  • Java中新建一個(gè)文件、目錄及路徑操作實(shí)例

    Java中新建一個(gè)文件、目錄及路徑操作實(shí)例

    這篇文章主要給大家介紹了關(guān)于Java中新建一個(gè)文件、目錄及路徑操作的相關(guān)資料,新建文件、目錄及路徑是我們?nèi)粘i_發(fā)中經(jīng)常會(huì)遇到的一個(gè)需求,本文通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-12-12
  • 修改Android應(yīng)用的樣式的一些關(guān)鍵點(diǎn)解析

    修改Android應(yīng)用的樣式的一些關(guān)鍵點(diǎn)解析

    這篇文章主要介紹了修改Android應(yīng)用的樣式的一些關(guān)鍵點(diǎn),即對影響外觀的theme跟style的相關(guān)修改,需要的朋友可以參考下
    2015-12-12

最新評(píng)論