如何使用SpringSecurity保護(hù)程序安全
首先,引入依賴:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
引入此依賴之后,你的web程序?qū)碛幸韵鹿δ埽?/p>
- 所有請求路徑都需要認(rèn)證
- 不需要特定的角色和權(quán)限
- 沒有登錄頁面,使用HTTP基本身份認(rèn)證
- 只有一個(gè)用戶,名稱為user
配置SpringSecurity
springsecurity配置項(xiàng),最好保存在一個(gè)單獨(dú)的配置類中:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
配置用戶認(rèn)證方式
首先,要解決的就是用戶注冊,保存用戶的信息。springsecurity提供四種存儲(chǔ)用戶的方式:
- 基于內(nèi)存(生產(chǎn)肯定不使用)
- 基于JDBC
- 基于LDAP
- 用戶自定義(最常用)
使用其中任意一種方式,需要覆蓋configure(AuthenticationManagerBuilder auth)方法:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
}
}
1.基于內(nèi)存
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("zhangsan").password("123").authorities("ROLE_USER")
.and()
.withUser("lisi").password("456").authorities("ROLE_USER");
}
2.基于JDBC
@Autowired
DataSource dataSource;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication()
.dataSource(dataSource);
}
基于JDBC的方式,你必須有一些特定表表,而且字段滿足其查詢規(guī)則:
public static final String DEF_USERS_BY_USERNAME_QUERY = "select username,password,enabled " + "from users " + "where username = ?"; public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY = "select username,authority " + "from authorities " + "where username = ?"; 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)然,你可以對這些語句進(jìn)行一下修改:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery("select username, password, enabled from Users " +
"where username=?")
.authoritiesByUsernameQuery("select username, authority from UserAuthorities " +
"where username=?");
這有一個(gè)問題,你數(shù)據(jù)庫中的密碼可能是一種加密方式加密過的,而用戶傳遞的是明文,比較的時(shí)候需要進(jìn)行加密處理,springsecurity也提供了相應(yīng)的功能:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery("select username, password, enabled from Users " +
"where username=?")
.authoritiesByUsernameQuery("select username, authority from UserAuthorities " +
"where username=?")
.passwordEncoder(new StandardPasswordEncoder("53cr3t");
passwordEncoder方法傳遞的是PasswordEncoder接口的實(shí)現(xiàn),其默認(rèn)提供了一些實(shí)現(xiàn),如果都不滿足,你可以實(shí)現(xiàn)這個(gè)接口:
- BCryptPasswordEncoder
- NoOpPasswordEncoder
- Pbkdf2PasswordEncoder
- SCryptPasswordEncoder
- StandardPasswordEncoder(SHA-256)
3.基于LDAP
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.userSearchBase("ou=people")
.userSearchFilter("(uid={0})")
.groupSearchBase("ou=groups")
.groupSearchFilter("member={0}")
.passwordCompare()
.passwordEncoder(new BCryptPasswordEncoder())
.passwordAttribute("passcode")
.contextSource()
.root("dc=tacocloud,dc=com")
.ldif("classpath:users.ldif");
4.用戶自定義方式(最常用)
首先,你需要一個(gè)用戶實(shí)體類,它實(shí)現(xiàn)UserDetails接口,實(shí)現(xiàn)這個(gè)接口的目的是為框架提供更多的信息,你可以把它看作框架使用的實(shí)體類:
@Data
public class User implements UserDetails {
private Long id;
private String username;
private String password;
private String fullname;
private String city;
private String phoneNumber;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public boolean isAccountNonExpired() {
return false;
}
@Override
public boolean isAccountNonLocked() {
return false;
}
@Override
public boolean isCredentialsNonExpired() {
return false;
}
@Override
public boolean isEnabled() {
return false;
}
}
有了實(shí)體類,你還需要Service邏輯層,springsecurity提供了UserDetailsService接口,見名知意,你只要通過loadUserByUsername返回一個(gè)UserDetails對象就成,無論是基于文件、基于數(shù)據(jù)庫、還是基于LDAP,剩下的對比判斷交個(gè)框架完成:
@Service
public class UserService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
return null;
}
}
最后,進(jìn)行應(yīng)用:
@Autowired
private UserDetailsService userDetailsService;
@Bean
public PasswordEncoder encoder() {
return new StandardPasswordEncoder("53cr3t");
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(encoder());
}
配置認(rèn)證路徑
知道了如何認(rèn)證,但現(xiàn)在有幾個(gè)問題,比如,用戶登錄頁面就不需要認(rèn)證,可以用configure(HttpSecurity http)對認(rèn)證路徑進(jìn)行配置:
@Override
protected void configure(HttpSecurity http) throws Exception {
}
你可以通過這個(gè)方法,實(shí)現(xiàn)以下功能:
- 在提供接口服務(wù)前,判斷請求必須滿足某些條件
- 配置登錄頁面
- 允許用戶注銷登錄
- 跨站點(diǎn)偽造請求防護(hù)
1.保護(hù)請求
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/design", "/orders").hasRole("ROLE_USER")
.antMatchers(“/”, "/**").permitAll();
}
要注意其順序,除了hasRole和permitAll還有其它訪問認(rèn)證方法:
| 方法 | 作用 |
|---|---|
| access(String) | 如果給定的SpEL表達(dá)式的計(jì)算結(jié)果為true,則允許訪問 |
| anonymous() | 允許訪問匿名用戶 |
| authenticated() | 允許訪問經(jīng)過身份驗(yàn)證的用戶 |
| denyAll() | 無條件拒絕訪問 |
| fullyAuthenticated() | 如果用戶完全通過身份驗(yàn)證,則允許訪問 |
| hasAnyAuthority(String...) | 如果用戶具有任何給定權(quán)限,則允許訪問 |
| hasAnyRole(String...) | 如果用戶具有任何給定角色,則允許訪問 |
| hasAuthority(String) | 如果用戶具有給定權(quán)限,則允許訪問 |
| hasIpAddress(String) | 如果請求來自給定的IP地址,則允許訪問 |
| hasRole(String) | 如果用戶具有給定角色,則允許訪問 |
| not() | 否定任何其他訪問方法的影響 |
| permitAll() | 允許無條件訪問 |
| rememberMe() | 允許通過remember-me進(jìn)行身份驗(yàn)證的用戶訪問 |
大部分方法是為特定方式準(zhǔn)備的,但是access(String)可以使用SpEL進(jìn)一些特殊的設(shè)置,但其中很大一部分也和上面的方法相同:
| 表達(dá)式 | 作用 |
|---|---|
| authentication | 用戶的身份驗(yàn)證對象 |
| denyAll | 始終評估為false |
| hasAnyRole(list of roles) | 如果用戶具有任何給定角色,則為true |
| hasRole(role) | 如果用戶具有給定角色,則為true |
| hasIpAddress(IP address) | 如果請求來自給定的IP地址,則為true |
| isAnonymous() | 如果用戶是匿名用戶,則為true |
| isAuthenticated() | 如果用戶已通過身份驗(yàn)證,則為true |
| isFullyAuthenticated() | 如果用戶已完全通過身份驗(yàn)證,則為true(未通過remember-me進(jìn)行身份驗(yàn)證) |
| isRememberMe() | 如果用戶通過remember-me進(jìn)行身份驗(yàn)證,則為true |
| permitAll | 始終評估為true |
| principal | 用戶的主要對象 |
示例:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/design", "/orders").access("hasRole('ROLE_USER')")
.antMatchers(“/”, "/**").access("permitAll");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/design", "/orders").access("hasRole('ROLE_USER') && " +
"T(java.util.Calendar).getInstance().get("+"T(java.util.Calendar).DAY_OF_WEEK) == " + "T(java.util.Calendar).TUESDAY")
.antMatchers(“/”, "/**").access("permitAll");
}
2.配置登錄頁面
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/design", "/orders").access("hasRole('ROLE_USER')")
.antMatchers(“/”, "/**").access("permitAll")
.and()
.formLogin()
.loginPage("/login");
}
// 增加視圖處理器
@Overridepublic void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("home");
registry.addViewController("/login");
}
默認(rèn)情況下,希望傳遞的是username和password,當(dāng)然你可以修改:
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/authenticate")
.usernameParameter("user")
.passwordParameter("pwd")
也可修改默認(rèn)登錄成功的頁面:
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/design")
3.配置登出
.and()
.logout()
.logoutSuccessUrl("/")
4.csrf攻擊
springsecurity默認(rèn)開啟了防止csrf攻擊,你只需要在傳遞的時(shí)候加上:
<input type="hidden" name="_csrf" th:value="${_csrf.token}"/>
當(dāng)然,你也可以關(guān)閉,但是不建議這樣做:
.and()
.csrf()
.disable()
知道用戶是誰
僅僅控制用戶登錄有時(shí)候是不夠的,你可能還想在程序的其它地方獲取已經(jīng)登錄的用戶信息,有幾種方式可以做到:
- 將Principal對象注入控制器方法
- 將Authentication對象注入控制器方法
- 使用SecurityContextHolder獲取安全上下文
- 使用@AuthenticationPrincipal注解方法
1.將Principal對象注入控制器方法
@PostMappingpublic String processOrder(@Valid Order order, Errors errors,SessionStatus sessionStatus,Principal principal) {
...
User user = userRepository.findByUsername(principal.getName());
order.setUser(user);
...
}
2.將Authentication對象注入控制器方法
@PostMappingpublic String processOrder(@Valid Order order, Errors errors, SessionStatus sessionStatus, Authentication authentication) {
...
User user = (User) authentication.getPrincipal();
order.setUser(user);
...
}
3.使用SecurityContextHolder獲取安全上下文
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); User user = (User) authentication.getPrincipal();
4.使用@AuthenticationPrincipal注解方法
@PostMappingpublic String processOrder(@Valid Order order, Errors errors,SessionStatus sessionStatus, @AuthenticationPrincipal User user) {
if (errors.hasErrors()) {
return "orderForm";
}
order.setUser(user);
orderRepo.save(order);
sessionStatus.setComplete();
return "redirect:/";
}
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
記一次集成swagger2(Knife4j)在線文檔提示:Knude4j文檔請求異常的解決辦法
Knife4j是一個(gè)集Swagger2 和 OpenAPI3為一體的增強(qiáng)解決方案,下面這篇文章主要給大家介紹了關(guān)于一次集成swagger2(Knife4j)在線文檔提示:Knude4j文檔請求異常的解決辦法,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-02-02
SpringCloud網(wǎng)關(guān)(Zuul)如何給多個(gè)微服務(wù)之間傳遞共享參數(shù)
這篇文章主要介紹了SpringCloud網(wǎng)關(guān)(Zuul)如何給多個(gè)微服務(wù)之間傳遞共享參數(shù),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
SpringBoot配置多個(gè)數(shù)據(jù)源超簡單步驟(連接多個(gè)數(shù)據(jù)庫)
公司項(xiàng)目有連接多個(gè)不同數(shù)據(jù)庫的需求,特研究了一下,根據(jù)網(wǎng)上的資料,這篇文章主要給大家介紹了關(guān)于SpringBoot配置多個(gè)數(shù)據(jù)源(連接多個(gè)數(shù)據(jù)庫)的相關(guān)資料,需要的朋友可以參考下2024-05-05
Java?輸入輸出?IO?NIO?AIO三兄弟對比分析對比分析
這篇文章主要為大家介紹了Java?輸入輸出?IO?NIO?AIO三兄弟對比分析對比分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04
SpringBoot中使用AOP實(shí)現(xiàn)日志記錄功能
AOP的全稱是Aspect-Oriented Programming,即面向切面編程(也稱面向方面編程),它是面向?qū)ο缶幊蹋∣OP)的一種補(bǔ)充,目前已成為一種比較成熟的編程方式,本文給大家介紹了SpringBoot中使用AOP實(shí)現(xiàn)日志記錄功能,需要的朋友可以參考下2024-05-05

