說明 springboot 版本 2.0.3
源碼地址:點擊跳轉
系列
一、 介紹
Spring Security 是一個能夠為基於 Spring 的企業應用系統提供聲明式的安全訪問控制解決方案的安全框架。它提供了一組可以在 Spring 應用上下文中配置的 Bean,充分利用了 Spring IoC,DI(控制反轉 Inversion of Control ,DI:Dependency Injection 依賴注入)和 AOP(面向切面編程)功能,為應用系統提供聲明式的安全訪問控制功能,減少了為企業系統安全控制編寫大量重復代碼的工作。
二、 環境搭建
建立 springboot2 項目,加入 security 依賴,mybatis 依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
數據庫為傳統的用戶--角色--權限,權限表記錄了 url 和 method,springboot 配置文件如下:
mybatis:
type-aliases-package: com.example.demo.entity
server:
port: 8081
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=true
username: root
password: 123456
http:
encoding:
charset: utf-8
enabled: true
springboot 啟動類中加入如下代碼,設置路由匹配規則。
@Override
protected void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(false) //設置路由是否后綴匹配,譬如/user能夠匹配/user.,/user.aa
.setUseTrailingSlashMatch(false); //設置是否后綴路徑匹配,比如/user能夠匹配/user,/user/
}
三、 security 配置
默認情況下 security 是無需任何自定義配置就可使用的,我們不考慮這種方式,直接講如何個性化登錄過程。
1、 建立 security 配置文件,目前配置文件中還沒有任何配置。
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
2、 個性化登錄,security 中的登錄如下:
- security 需要一個 user 的實體類實現
UserDetails
接口,該實體類最后與系統中用戶的實體類分開,代碼如下:
public class SecurityUser implements UserDetails{
private static final long serialVersionUID = 1L;
private String password;
private String name;
List<GrantedAuthority> authorities;
public SecurityUser(string name,string password) {
this.id = id;
this.password = password;
this.name = name;
this.age = age;
}
public void setAuthorities(List<GrantedAuthority> authorities) {
this.authorities = authorities;
}
@Override
public Collection<GrantedAuthority> getAuthorities() {
return this.authorities;
}
@Override //獲取校驗用戶名
public String getUsername() {
return String.valueOf(this.id);
}
@Override //獲取校驗用密碼
public String getPassword() {
return password;
}
@Override //賬戶是否未過期
public boolean isAccountNonExpired() {
// TODO Auto-generated method stub
return true;
}
@Override //賬戶是否未鎖定
public boolean isAccountNonLocked() {
// TODO Auto-generated method stub
return true;
}
@Override //帳戶密碼是否未過期,一般有的密碼要求性高的系統會使用到,比較每隔一段時間就要求用戶重置密碼
public boolean isCredentialsNonExpired() {
// TODO Auto-generated method stub
return true;
}
@Override //賬戶是否可用
public boolean isEnabled() {
// TODO Auto-generated method stub
return true;
}
}
- 編寫了實體類還需要編寫一個服務類 SecurityService 實現
UserDetailsService
接口,重寫 loadByUsername 方法,通過這個方法根據用戶名獲取用戶信息,代碼如下:
@Component
public class SecurityUserService implements UserDetailsService {
@Autowired
private JurisdictionMapper jurisdictionMapper;
@Autowired
private UserMapper userMapper;
private Logger log = LoggerFactory.getLogger(this.getClass());
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("登錄用戶id為:{}",username);
int id = Integer.valueOf(username);
User user = userMapper.getById(id);
if(user==null) {
//拋出錯誤,用戶不存在
throw new UsernameNotFoundException("用戶名 "+username+"不存在");
}
//獲取用戶權限
List<GrantedAuthority> authorities = new ArrayList<>();
List<Jurisdiction> jurisdictions = jurisdictionMapper.selectByUserId(id);
for(Jurisdiction item : jurisdictions) {
GrantedAuthority authority = new MyGrantedAuthority(item.getMethod(),item.getUrl());
authorities.add(authority);
}
SecurityUser securityUser = new SecurityUser(user.getName(),user.getPassword(),authority):
user.setAuthorities(authorities);
return securityUser;
}
}
- 通常我們會對密碼進行加密,所有還要編寫一個 passwordencode 類,實現 PasswordEncoder 接口,代碼如下:
@Component
public class MyPasswordEncoder implements PasswordEncoder {
private Logger log = LoggerFactory.getLogger(this.getClass());
@Override //不清楚除了在下面方法用到還有什么用處
public String encode(CharSequence rawPassword) {
return StringUtil.StringToMD5(rawPassword.toString());
}
//判斷密碼是否匹配
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return encodedPassword.equals(this.encode(rawPassword));
}
}
3、 編輯配置文件
- 編寫 config Bean 以使用上面定義的驗證邏輯,securityUserService、myPasswordEncoder 通過@Autowired 引入。
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(securityUserService)
.passwordEncoder(myPasswordEncoder);
}
- 然后編寫 configure Bean(和上一個不一樣,參數不同),實現 security 驗證邏輯,代碼如下:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf() //跨站
.disable() //關閉跨站檢測
.authorizeRequests()//驗證策略策略鏈
.antMatchers("/public/**").permitAll()//無需驗證路徑
.antMatchers("/login").permitAll()//放行登錄
.antMatchers(HttpMethod.GET, "/user").hasAuthority("getAllUser")//擁有權限才可訪問
.antMatchers(HttpMethod.GET, "/user").hasAnyAuthority("1","2")//擁有任一權限即可訪問
//角色類似,hasRole(),hasAnyRole()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/public/unlogin") //未登錄跳轉頁面,設置了authenticationentrypoint后無需設置未登錄跳轉頁面
.loginProcessingUrl("/public/login")//處理登錄post請求接口,無需自己實現
.successForwardUrl("/success")//登錄成功轉發接口
.failureForwardUrl("/failed")//登錄失敗轉發接口
.usernameParameter("id") //修改用戶名的表單name,默認為username
.passwordParameter("password")//修改密碼的表單name,默認為password
.and()
.logout()//自定義登出
.logoutUrl("/public/logout") //自定義登出api,無需自己實現
.logoutSuccessUrl("public/logoutSuccess")
}
到這里便可實現 security 與 springboot 的基本整合。
四、實現記住我功能
1、 建表
記住我功能需要數據庫配合實現,首先要在數據庫建一張表用戶保存 cookie 和用戶名,數據庫建表語句如下:不能做修改
CREATE TABLE `persistent_logins` (
`username` varchar(64) NOT NULL,
`series` varchar(64) NOT NULL,
`token` varchar(64) NOT NULL,
`last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`series`)
)
2、 編寫 rememberMeservice Bean
代碼如下:
@Bean
public RememberMeServices rememberMeServices(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
PersistentTokenBasedRememberMeServices rememberMeServices =
new PersistentTokenBasedRememberMeServices("INTERNAL_SECRET_KEY",securityUserService,jdbcTokenRepository);
//還可設置許多其他屬性
rememberMeServices.setCookieName("kkkkk"); //客戶端cookie名
return rememberMeServices;
}
dataSource 為@Autowired 引入
3、 配置文件設置 remember
在 config(HttpSecurity http)中加入記住我功能
.rememberMe()
.rememberMeServices(rememberMeServices())
.key("INTERNAL_SECRET_KEY")
在登錄表單中設置 remember-me 即可實現記住我功能。