您好,登錄后才能下訂單哦!
這篇文章主要介紹“Spring怎么實現(xiàn)用戶存儲”的相關(guān)知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“Spring怎么實現(xiàn)用戶存儲”文章能幫助大家解決問題。
首先,我們看下如何在認證的過程中配置訪問用戶數(shù)據(jù)的服務(wù)。
針對上節(jié)演示的案例,我們需要改進的用戶存儲,也就是用戶名、密碼以及其他信息存儲的地方,在進行認證決策的時候,會對其行檢索。而不是寫死的用戶名和隨機的密碼。
Spring Security非常靈活,能夠基于各種數(shù)據(jù)存儲來認證用戶。它內(nèi)置了許多常見的用戶存儲場景。如: 內(nèi)存,關(guān)系型數(shù)據(jù)庫,以及LDAP。
當(dāng)然,我們也可以編寫并插入自定義的用戶存儲實現(xiàn)。借助Spring Security的Java配置,我們能夠很容易地配置一個或多個數(shù)據(jù)存儲方案。那我們就從最簡單的開始:在內(nèi)存中維護用戶存儲。
我們在上節(jié)的基礎(chǔ)上進行改造,因為我們的安全配置類 SecurityConfig 擴展了 WebSecurityConfigurerAdapter ,因此配置用戶存儲最簡單的方法就是 重載 configure() 方法。
/** * @author itguang * @create @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter{ @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("itguang").password("123456").roles("USER").and() .withUser("admin").password("123456").roles("ADMIN"); } }
我們可以看到,configure方法中的 AuthenticationManagerBuilder 類型的參數(shù),采用了構(gòu)造者風(fēng)格風(fēng)格的接口來構(gòu)建認證配置。
通過簡單的調(diào)用 inMemoryAuthentication() 就能啟用基于內(nèi)存的的用戶存儲。
通過 withUser 我們添加了兩個基于內(nèi)存存儲的用戶,并且分別授予的不同的角色。
withUser()方法返回的是 UserDetailsManagerConfigurer.UserDetailsBuilder ,這個對象提供了多個進一步配置用戶的方法。
接下來,就可以啟動項目進行測試了。首先還是訪問: http://localhost/hello ,會自動動跳轉(zhuǎn)到 http://localhost/login 讓我們?nèi)サ顷憽?
這次我們就不用再去啟動日志中找生成的隨機密碼了,我們用 上面配置的兩個用戶分別進行測試,發(fā)現(xiàn)都能夠正確登陸,到這里一個簡單的基于內(nèi)存的用戶存儲就完成了。
UserDetailsManagerConfigurer.UserDetailsBuilder除了password()、roles()和and()方法以外,還有其他的幾個方法可以用來配置內(nèi)存用戶存儲中的用戶信息
如下表所示:
但是通常來說,我們的系統(tǒng)肯定不止兩個用戶,基于內(nèi)存的用戶存儲還是不能滿足我們的要求,我們期待最好能將數(shù)據(jù)保存在某種關(guān)系型數(shù)據(jù)庫中。
沒錯,Spring Security也為我們提供了基于關(guān)系型數(shù)據(jù)庫的 用戶存儲。
用戶數(shù)據(jù)通常會存儲在關(guān)系型數(shù)據(jù)庫中并通過JDBC進行訪問。為了配置Spring Security配置以JDBC為支撐的用戶存儲。我們可以使用jdbcAuthentication()方法。
所需的最簡單的配置如下所示:
/** * @author itguang * @create @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter{ @Autowired private DataSource dataSource; //基于數(shù)據(jù)庫表的用戶存儲 @Override public void configure(AuthenticationManagerBuilder auth) throws
在這里我們只需要配置一個dataSource即可,這樣就可以訪問關(guān)系型數(shù)據(jù)庫了。
其中 dataSource是通過自動裝配的技巧得到的。在springboot下 dataSource 是自動裝配的。
我們只需要引入如下依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
并在appliciation.properties 中配置spring.datasource 相關(guān)屬性即可。
spring.datasource.driverClassName=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/test?prepStmtCacheSize=517&cachePrepStmts=true&autoReconnect=true&characterEncoding=utf-8&allowMultiQueries=true spring.datasource.username=root spring.datasource.password=root
Spring Boot就會使用該配置創(chuàng)建一個DataSource。并裝配為Spring的一個bean。因此我們可以直接使用 @Autowired 進行注入。
好了,直到目前為止,對基于數(shù)據(jù)庫的用戶存儲已經(jīng)配置完畢,你可能回想,那既然基于數(shù)據(jù)庫了,肯定要在數(shù)據(jù)庫創(chuàng)建相應(yīng)的表了。那表結(jié)構(gòu)又是怎么樣的呢?
不要著急,接下來我們就開始數(shù)據(jù)庫表結(jié)構(gòu)的創(chuàng)建。
回到我們的 SpringSecurityConfig 的配置類,我們看下 jdbcAuthentication 的返回值是: JdbcUserDetailsManagerConfigurer,我們再看看 JdbcUserDetailsManagerConfigurer
類中還有哪些方法。
我們主要看下黃框標(biāo)識出的幾個方法,
/** * Populates the {@link DataSource} to be used. This is the only required attribute. * * @param dataSource the {@link DataSource} to be used. Cannot be null. * @return * @throws public JdbcUserDetailsManagerConfigurer<B> dataSource(DataSource dataSource) throws Exception { this.dataSource = dataSource; getUserDetailsService().setDataSource(dataSource); return this; } /** * Sets the query to be used for finding a user by their username. For example: * * <code> * select username,password,enabled from users where username = ? * </code> * @param query The query to use for selecting the username, password, and if the user * is enabled by username. Must contain a single parameter for the username. * @return The {@link JdbcUserDetailsManagerConfigurer} used for additional * customizations * @throws public JdbcUserDetailsManagerConfigurer<B> usersByUsernameQuery(String query) throws Exception { getUserDetailsService().setUsersByUsernameQuery(query); return this; } /** * Sets the query to be used for finding a user's authorities by their username. For * example: * * <code> * select username,authority from authorities where username = ? * </code> * * @param query The query to use for selecting the username, authority by username. * Must contain a single parameter for the username. * @return The {@link JdbcUserDetailsManagerConfigurer} used for additional * customizations * @throws public JdbcUserDetailsManagerConfigurer<B> authoritiesByUsernameQuery(String query) throws Exception { getUserDetailsService().setAuthoritiesByUsernameQuery(query); return this; } /** * An SQL statement to query user's group authorities given a username. For example: * * <code> * select * g.id, g.group_name, ga.authority * from * groups g, group_members gm, group_authorities ga * where * gm.username = ? and g.id = ga.group_id and g.id = gm.group_id * </code> * * @param query The query to use for selecting the authorities by group. Must contain * a single parameter for the username. * @return The {@link JdbcUserDetailsManagerConfigurer} used for additional * customizations * @throws public JdbcUserDetailsManagerConfigurer<B> groupAuthoritiesByUsername(String query) throws Exception { JdbcUserDetailsManager userDetailsService = getUserDetailsService(); userDetailsService.setEnableGroups(true); userDetailsService.setGroupAuthoritiesByUsernameQuery(query); return this; }
通過查看源碼以及方法上的注釋,我們發(fā)現(xiàn)這三個方法主要是用來配置 用戶查詢(usersByUsernameQuery),權(quán)限查詢(authoritiesByUsernameQuery),
權(quán)限組查詢(groupAuthoritiesByUsername)的sql語句的。并且Spring已經(jīng)給我們配置了默認的實現(xiàn)。
//默認的用戶查詢sql public static final String DEF_USERS_BY_USERNAME_QUERY = "select username,password,enabled " + "from users " + "where username = ?"; //默認的權(quán)限查詢sql public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY = "select username,authority " + "from authorities " + "where username = ?"; //默認的權(quán)限組查詢sql public static final String DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY = "select g.id, g.group_name, ga.authority " + "from groups g, group_members gm, group_authorities ga " + "where gm.username = ? " + "and g.id = ga.group_id " + "and g.id = gm.group_id";
當(dāng)然我們完全可以自定義這些語句。就像下面這樣
@Autowired private DataSource dataSource; //基于數(shù)據(jù)庫表的用戶存儲 @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.jdbcAuthentication() .dataSource(dataSource) .usersByUsernameQuery("select username,password,enabled,email from users where username=?") .authoritiesByUsernameQuery("select username,authority from authorities where username = ?") .groupAuthoritiesByUsername("select g.id, g.group_name, ga.authority from groups g, group_members gm, group_authorities ga where gm.username = ? and g.id = ga.group_id and g.id = gm.group_id"); }
接下來,就是按照我們定義的這些語句進行表結(jié)構(gòu)的創(chuàng)建了。創(chuàng)建完之后,所有表模型如下圖:
表結(jié)構(gòu)創(chuàng)建語句如下:
DROP TABLE IF EXISTS `authorities`; CREATE TABLE `authorities` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(20) DEFAULT NULL, `authority` varchar(50) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT -- ---------------------------- -- Table structure for groups -- ---------------------------- DROP TABLE IF EXISTS `groups`; CREATE TABLE `groups` ( `id` int(11) NOT NULL AUTO_INCREMENT, `group_name` varchar(50) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT -- ---------------------------- -- Table structure for group_authorities -- ---------------------------- DROP TABLE IF EXISTS `group_authorities`; CREATE TABLE `group_authorities` ( `group_id` int(11) NOT NULL AUTO_INCREMENT, `authority` varchar(50) DEFAULT NULL, PRIMARY KEY (`group_id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT -- ---------------------------- -- Table structure for group_members -- ---------------------------- DROP TABLE IF EXISTS `group_members`; CREATE TABLE `group_members` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(20) DEFAULT NULL, `group_id` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT -- ---------------------------- -- Table structure for users -- ---------------------------- DROP TABLE IF EXISTS `users`; CREATE TABLE `users` ( `id` int(8) NOT NULL AUTO_INCREMENT, `username` varchar(20) DEFAULT NULL, `password` varchar(50) DEFAULT NULL, `enabled` tinyint(4) DEFAULT NULL, `email` varchar(50) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT
現(xiàn)在,基于數(shù)據(jù)庫的用戶存儲已經(jīng)配置完畢,雖然看起來比基于內(nèi)存的用戶存儲代碼多,但仔細查看源碼,還是不難理解的。
我們重新啟動應(yīng)用程序,瀏覽器訪問: http://localhost/hello ,不出意外的有跳轉(zhuǎn)到了 http://localhost/login 讓我們進行登陸。但是現(xiàn)在我們的數(shù)據(jù)庫中并沒有用戶。
所以我們還需要在數(shù)據(jù)庫中創(chuàng)建用戶,打開數(shù)據(jù)庫,在users表中添加一個用戶,如下:
我們填入我們新加入的用戶名和密碼,點擊登錄,就會發(fā)現(xiàn),登陸失敗:
可以看到錯誤信息為: Reason: Bad credentials
這是因為雖然我們只添加了用戶,但是并沒有為此用戶分配權(quán)限,在 authoritiesByUsernameQuery 執(zhí)行sql時查詢到的就是null,就會返回 Bad credentials。
我們只需要在數(shù)據(jù)庫的 authorities 表中為 itguang 用戶添加一個權(quán)限即可。
再次登陸,就會發(fā)現(xiàn)已經(jīng)可以登陸成功。
**對于最后一個 groupAuthoritiesByUsername 根據(jù)用戶名查詢用戶組權(quán)限的sql,即使查詢?yōu)閚ull,只要是用戶已經(jīng)在 authorities 表中分配了權(quán)限,
就可以登陸成功。對于 權(quán)限–用戶–角色(用戶組) 的關(guān)系,不清楚的,可以去網(wǎng)上搜一下,有很多文章講的很清楚。**
最后再補充一個小知識,看一下上面的認證查詢,它會預(yù)期用戶名密碼存儲在了數(shù)據(jù)庫中。這里唯一的問題就是,如果使用明文存儲密碼,會帶來安全性問題。
但是如果數(shù)據(jù)庫中的密碼進行轉(zhuǎn)碼的話,認證就會失敗,因為它與用戶提交的明文密碼并不匹配。
為了解決這個問題,我們需要借助passwordEncoder()方法指定一個密碼轉(zhuǎn)碼器(encoder)。
@Autowired private DataSource dataSource; //基于數(shù)據(jù)庫表的用戶存儲 @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.jdbcAuthentication() .dataSource(dataSource) .usersByUsernameQuery("select username,password,enabled,email from users where username=?") .authoritiesByUsernameQuery("select username,authority from authorities where username = ?") .groupAuthoritiesByUsername("select g.id, g.group_name, ga.authority from groups g, group_members gm, group_authorities ga where gm.username = ? and g.id = ga.group_id and g.id = gm.group_id") .passwordEncoder(new StandardPasswordEncoder("53cr3t")); }
passwordEncoder()方法可以接受Spring Security中PasswordEncoder接口的任意實現(xiàn)。
Spring Security的加密模塊包括了三個這樣的實現(xiàn):BCryptPasswordEncoder、NoOpPasswordEncoder和StandardPasswordEncoder。
上述的代碼中使用了StandardPasswordEncoder,采用的是 “SHA-256”算法加密實現(xiàn)。
但是如果內(nèi)置的實現(xiàn)無法滿足需求時,你可以提供自定義的實現(xiàn)。
關(guān)于“Spring怎么實現(xiàn)用戶存儲”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識,可以關(guān)注億速云行業(yè)資訊頻道,小編每天都會為大家更新不同的知識點。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。