java中Spring Security的實(shí)例詳解
spring security是一個(gè)多方面的安全認(rèn)證框架,提供了基于JavaEE規(guī)范的完整的安全認(rèn)證解決方案。并且可以很好與目前主流的認(rèn)證框架(如CAS,中央授權(quán)系統(tǒng))集成。使用spring security的初衷是解決不同用戶登錄不同應(yīng)用程序的權(quán)限問(wèn)題,說(shuō)到權(quán)限包括兩部分:認(rèn)證和授權(quán)。認(rèn)證是告訴系統(tǒng)你是誰(shuí),授權(quán)是指知道你是誰(shuí)后是否有權(quán)限訪問(wèn)系統(tǒng)(授權(quán)后一般會(huì)在服務(wù)端創(chuàng)建一個(gè)token,之后用這個(gè)token進(jìn)行后續(xù)行為的交互)。
spring security提供了多種認(rèn)證模式,很多第三方的認(rèn)證技術(shù)都可以很好集成:
- Form-based authentication (用于簡(jiǎn)單的用戶界面)
- OpenID 認(rèn)證
- Authentication based on pre-established request headers (such as Computer - Associates Siteminder)根據(jù)預(yù)先建立的請(qǐng)求頭進(jìn)行驗(yàn)證
- JA-SIG Central Authentication Service ( CAS, 一個(gè)開源的SSO系統(tǒng))
- Java Authentication and Authorization Service (JAAS)
這里只列舉了部分,后面會(huì)重點(diǎn)介紹如何集成CAS,搭建自己的認(rèn)證服務(wù)。
在spring boot項(xiàng)目中使用spring security很容易,這里介紹如何基于內(nèi)存中的用戶和基于數(shù)據(jù)庫(kù)進(jìn)行認(rèn)證。
準(zhǔn)備
pom依賴:
1
2
3
4
5
|
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> <version> 1.5 . 1 .RELEASE</version> </dependency> |
配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public UserDetailsService userDetailsService() { return new CustomUserDetailsService(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().withUser( "rhwayfun" ).password( "1209" ).roles( "USERS" ) .and().withUser( "admin" ).password( "123456" ).roles( "ADMIN" ); //auth.jdbcAuthentication().dataSource(securityDataSource); //auth.userDetailsService(userDetailsService()); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() //配置安全策略 //.antMatchers("/","/index").permitAll()//定義/請(qǐng)求不需要驗(yàn)證 .anyRequest().authenticated() //其余的所有請(qǐng)求都需要驗(yàn)證 .and() .formLogin() .loginPage( "/login" ) .defaultSuccessUrl( "/index" ) .permitAll() .and() .logout() .logoutSuccessUrl( "/login" ) .permitAll(); //定義logout不需要驗(yàn)證 http.csrf().disable(); } } |
這里需要覆蓋WebSecurityConfigurerAdapter的兩個(gè)方法,分別定義什么請(qǐng)求需要什么權(quán)限,并且認(rèn)證的用戶密碼分別是什么。
1
2
3
4
5
6
7
8
9
10
|
@Configuration public class WebMvcConfig extends WebMvcConfigurerAdapter { /** * 統(tǒng)一注冊(cè)純RequestMapping跳轉(zhuǎn)View的Controller */ @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController( "/login" ).setViewName( "/login" ); } } |
添加登錄跳轉(zhuǎn)的URL,如果不加這個(gè)配置也會(huì)默認(rèn)跳轉(zhuǎn)到/login下,所以這里還可以自定義登錄的請(qǐng)求路徑。
登錄頁(yè)面:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
<!DOCTYPE html> <%--<%@ taglib prefix= "spring" uri= "http://www.springframework.org/tags" %> <%@ taglib prefix= "c" uri= "http://java.sun.com/jsp/jstl/core" %>--%> <html> <head> <meta charset= "utf-8" > <title>Welcome SpringBoot</title> <script src= "/js/jquery-3.1.1.min.js" ></script> <script src= "/js/index.js" ></script> </head> <body> <form name= "f" action= "/login" method= "post" > <input id= "name" name= "username" type= "text" /><br> <input id= "password" name= "password" type= "password" ><br> <input type= "submit" value= "login" > <input name= "_csrf" type= "hidden" value= "${_csrf}" /> </form> <p id= "users" > </p> <script> $(function () { $( '[name=f]' ).focus() }) </script> </body> </html> |
基于內(nèi)存
SecurityConfig這個(gè)配置已經(jīng)是基于了內(nèi)存中的用戶進(jìn)行認(rèn)證的,
1
2
3
4
5
|
auth.inMemoryAuthentication() //基于內(nèi)存進(jìn)行認(rèn)證 .withUser( "rhwayfun" ).password( "1209" ) //用戶名密碼 .roles( "USERS" ) //USER角色 .and() .withUser( "admin" ).password( "123456" ).roles( "ADMIN" ); //ADMIN角色 |
訪問(wèn)首頁(yè)會(huì)跳轉(zhuǎn)到登錄頁(yè)面這成功,使用配置的用戶登錄會(huì)跳轉(zhuǎn)到首頁(yè)。
基于數(shù)據(jù)庫(kù)
基于數(shù)據(jù)庫(kù)會(huì)復(fù)雜一些,不過(guò)原理是一致的只不過(guò)數(shù)據(jù)源從內(nèi)存轉(zhuǎn)到了數(shù)據(jù)庫(kù)。從基于內(nèi)存的例子我們大概知道spring security認(rèn)證的過(guò)程:從內(nèi)存查找username為輸入值得用戶,如果存在著驗(yàn)證其角色時(shí)候匹配,比如普通用戶不能訪問(wèn)admin頁(yè)面,這點(diǎn)可以在controller層使用@PreAuthorize("hasRole('ROLE_ADMIN')")實(shí)現(xiàn),表示只有admin角色的用戶才能訪問(wèn)該頁(yè)面,spring security中角色的定義都是以ROLE_開頭,后面跟上具體的角色名稱。
如果要基于數(shù)據(jù)庫(kù),可以直接指定數(shù)據(jù)源即可:
1
|
auth.jdbcAuthentication().dataSource(securityDataSource); |
只不過(guò)數(shù)據(jù)庫(kù)標(biāo)是spring默認(rèn)的,包括三張表:users(用戶信息表)、authorities(用戶角色信息表)
以下是查詢用戶信息以及創(chuàng)建用戶角色的SQL(部分,詳細(xì)可到JdbcUserDetailsManager類查看):
1
2
3
4
5
|
public static final String DEF_CREATE_USER_SQL = "insert into users (username, password, enabled) values (?,?,?)" ; public static final String DEF_INSERT_AUTHORITY_SQL = "insert into authorities (username, authority) values (?,?)" ; public static final String DEF_USER_EXISTS_SQL = "select username from users where username = ?" ; |
那么,如果要自定義數(shù)據(jù)庫(kù)表這需要配置如下,并且實(shí)現(xiàn)UserDetailsService接口:
1
2
3
4
5
6
|
auth.userDetailsService(userDetailsService()); @Bean public UserDetailsService userDetailsService() { return new CustomUserDetailsService(); } |
CustomUserDetailsService實(shí)現(xiàn)如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
@Service public class CustomUserDetailsService implements UserDetailsService { /** Logger */ private static Logger log = LoggerFactory.getLogger(CustomUserDetailsService. class ); @Resource private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { if (StringUtils.isBlank(username)) { throw new IllegalArgumentException( "username can't be null!" ); } User user; try { user = userRepository.findByUserName(username); } catch (Exception e) { log.error( "讀取用戶信息異常," , e); return null ; } if (user == null ) { return null ; } List<UserAuthority> roles = userRepository.findRoles(user.getUserId()); List<SimpleGrantedAuthority> authorities = new ArrayList<>(); for (UserAuthority role : roles) { SimpleGrantedAuthority authority = new SimpleGrantedAuthority(String.valueOf(role.getAuthId())); authorities.add(authority); } return new org.springframework.security.core.userdetails.User(username, "1234" , authorities); } } |
我們需要實(shí)現(xiàn)loadUserByUsername方法,這里面其實(shí)級(jí)做了兩件事:查詢用戶信息并返回該用戶的角色信息。
數(shù)據(jù)庫(kù)設(shè)計(jì)如下:
數(shù)據(jù)庫(kù)設(shè)計(jì)
g_users:用戶基本信息表
g_authority:角色信息表
r_auth_user:用戶角色信息表,這里沒有使用外鍵約束。
使用mybatis generator生成mapper后,創(chuàng)建數(shù)據(jù)源SecurityDataSource。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
|
@Configuration @ConfigurationProperties (prefix = "security.datasource" ) @MapperScan (basePackages = DataSourceConstants.MAPPER_SECURITY_PACKAGE, sqlSessionFactoryRef = "securitySqlSessionFactory" ) public class SecurityDataSourceConfig { private String url; private String username; private String password; private String driverClassName; @Bean (name = "securityDataSource" ) public DataSource securityDataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName(driverClassName); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); return dataSource; } @Bean (name = "securityTransactionManager" ) public DataSourceTransactionManager securityTransactionManager() { return new DataSourceTransactionManager(securityDataSource()); } @Bean (name = "securitySqlSessionFactory" ) public SqlSessionFactory securitySqlSessionFactory( @Qualifier ( "securityDataSource" ) DataSource securityDataSource) throws Exception { final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); sessionFactory.setDataSource(securityDataSource); sessionFactory.setMapperLocations( new PathMatchingResourcePatternResolver() .getResources(DataSourceConstants.MAPPER_SECURITY_LOCATION)); return sessionFactory.getObject(); } public String getUrl() { return url; } public void setUrl(String url) { this .url = url; } public String getUsername() { return username; } public void setUsername(String username) { this .username = username; } public String getPassword() { return password; } public void setPassword(String password) { this .password = password; } public String getDriverClassName() { return driverClassName; } public void setDriverClassName(String driverClassName) { this .driverClassName = driverClassName; } } |
那么DAO UserRepository就很好實(shí)現(xiàn)了:
1
2
3
4
5
6
7
|
public interface UserRepository{ User findByUserName(String username); List<UserAuthority> findRoles( int userId); } |
在數(shù)據(jù)庫(kù)插入相關(guān)數(shù)據(jù),重啟項(xiàng)目。仍然訪問(wèn)首頁(yè)跳轉(zhuǎn)到登錄頁(yè)后輸入數(shù)據(jù)庫(kù)插入的用戶信息,如果成功跳轉(zhuǎn)到首頁(yè)這說(shuō)明認(rèn)證成功。
如有疑問(wèn)請(qǐng)留言或者到本站社區(qū)交流討論,感謝閱讀,希望能幫助到大家,謝謝大家對(duì)本站的支持!
原文鏈接:http://blog.csdn.net/u011116672/article/details/77428049