我的整理:
spring security 安全訪問控制
認證:authentication n. 證明;鑑定;證實
授權:authorization n. 授權,認可;批准,委任
請求 需要認證身份 纔可以訪問
groupId:org.springframework.boot
artifactId:spring-boot-starter-security
@EnableWebSecurity 默認打開
基於servlet 過濾器 對請求 進行攔截
最簡單配置:
spring.security.user.name(和password)=admin
普通配置:
extends WebSecurityConfigurerAdapter
核心3個方法,配置2個。
protected void configure(AuthenticationManagerBuilder auth)
配置用戶和角色的(userdetails)
public void configure(WebSecurity web)
什麼請求放行,什麼請求需要認證
protected void configure(HttpSecurity http)
配置filter鏈。忽略哪些請求。
最簡單測試內存配置:
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();//單項不可逆的密碼
InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> userConfig= auth.inMemoryAuthentication() .passwordEncoder(passwordEncoder);
userConfig.withUser("admin").password("123456").authorities("ROLE_USER", "ROLE_ADMIN");
authorities可寫成 .roles("USER") 不需要加ROLE_
普通的數據庫認證(userdetails):
3個表。role,user,user_role 關聯表
角色表需有字段role_name用於存放:ROLE_ADMIN等角色
用戶表需有字段:user_name,pwd,available (int爲1的標識,1是啓用用戶,0是鎖死)
關聯表,多對多關係的表。需有:user_id 和 role_id字段。
核心有兩個sql:
1,根據用戶名查出用戶表的所有數據:
SELECT
id,
user_name,
available
FROM
user
WHERE
user_name = ''
2,根據用戶名查詢出這個用戶旗下的所有角色(3個表關聯)
SELECT
r.id,
r.role_name
FROM
role r,
user_role ur,
user u
WHERE
r.id = ur.role_id
AND ur.user_id = u.id
AND u.user_name = ''
開始構建userDetails,
implements UserDetailsService
查出一個用戶,查出用戶下的所有權限。
new User 需要參數爲用戶名,密碼,和 權限list
// 權限列表
List<GrantedAuthority> authorityList = new ArrayList<>();
// 賦予查詢到的角色
for (DatabaseRole role : roleList) {
GrantedAuthority authority
= new SimpleGrantedAuthority(role.getRoleName());
authorityList.add(authority);
}
UserDetails userDetails = new User(dbUser.getUserName(),
dbUser.getPwd(), authorityList);
依然是配置第一個方法,service即是上面構建的
PasswordEncoder passwordEncoder = new Pbkdf2PasswordEncoder(this.secret);
auth.userDetailsService(UserDetailsService).passwordEncoder(passwordEncoder);
secret可以寫在配置文件裏:
@Value("${system.secret}")
private String secret = null;
配置第二個方法(public void configure(WebSecurity web)),不同用戶授予不同的權限。
最簡單爲:
認證的,所有請求,都能訪問。啓用登錄。
http.authorizeRequests().anyRequest().authenticated() (相同的是:.antMatchers("/**").permitAll())
.and().formLogin().and().httpBasic();
普通配置:
http.authorizeRequests()
.antMatchers("/user/welcome").hasAnyRole("USER", "ADMIN")//有任意一個即可訪問
.antMatchers("/admin/**").hasAuthority("ROLE_ADMIN") //需要管理員權限
.anyRequest().permitAll() (其他登陸後可訪問,或配 anonymous()任意訪問)
或者spring el通配符配置
.antMatchers("/user/**").access("hasRole('USER') or hasRole('ADMIN')")
.antMatchers("/admin/welcome1").access("hasAuthority('ROLE_ADMIN') && isFullyAuthenticated()")
isFullyAuthenticated()不能使用記住我訪問
.requiresSecure() 只有https才能訪問。
.requiresInsecure() 不使用https
跨域請求,默認不能。可以禁用此功能。
http.csrf().disable().authorizeRequests()
禁止可以csrf攻擊。
不禁止,即配合_csrf字段使用:
<input type="hidden" id="${_csrf.parameterName}"
name="${_csrf.parameterName}" value="${_csrf.token}" />
rememberMe().tokenValiditySeconds(86400).key("remember-me-key")
開啓記住我,時間是一天,參數是秒。並設置cookie的鍵
<input id="remember_me" name="remember-me" type="checkbox">
自定義登錄和退出頁面:
.and().formLogin().loginPage("/login/page").defaultSuccessUrl("/admin/welcome1");
.and().logout().logoutUrl("/logout/page") .logoutSuccessUrl("/welcome");
起飛了
security基礎
authentication n. 證明;鑑定;證實
authorization n. 授權,認可;批准,委任
-
spring security 安全訪問控制
-
rest 是構建微服務 常見的系統集成的方案
-
有些 請求 需要用戶認證身份 纔可以進行
-
與第三方公司合作,需要驗證合作方 身份才能進行處理業務
-
聲明式 安全訪問 控制功能
security pom
- 引入pom即可,默認是打開的。
- 也可用註解:@EnableWebSecurity 開啓。非web 用@EnableGlobalAuthentication。已經包含
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
-
servlet 過濾器 (filter) 對請求 進行攔截,filter通過自己的驗證邏輯 來決定 是否放行請求
-
security 也是基於這一原理, 進入 DispatcherServlet 前進行攔截,從而決定是否放行
- 提供了,Dele gating Filter Proxy
- 全註解:@EnableWebSecurity
security 原理
-
啓用了 Spring Security,Ioc容器 會爲你創建 spring Security Filter Chain
-
是一個特殊的攔截器,也實現了 filter接口,類型爲 : filter chain Proxy
-
security操作的過程中 會提供 Servlet 過濾器 Delegation Filter Proxy
-
這個過濾器 會通過 Ioc 容器 獲取 Spring Security 所創建的 Filter Chain Proxy對象
-
這個對象存在一個攔截器列表,(用戶驗證,跨站點請求僞造)
-
Filter Chain Proxy
- 註冊Filter,允許註冊自定義的filter來實現對應的攔截請求
-
實現了大部分常用的安全功能。並提供了相應的機制來簡化 開發者的工作
-
用戶名是 user ,密碼默認會打印
security 配置
#自定義用戶名和密碼
spring.security.user.name=myuser
spring.security.user.password=123456
spring:
security:
filter:
order: -100 #security 過濾器排序
dispatcher-types: async,error,request #安全過濾器責任鏈的分發類型
user:
name: user
password: 123456
roles:
oauth2:
client:
provider: #oauth2 提供者詳細配置信息
registration: #客戶端登記信息
WebSecurity Configurer Adapter自定義
-
FilterChainProxy對象,過濾器 DelegatingFilterProxy的攔截邏輯
-
給 FilterChainProxy對象初始化,security提供了 SecurityConfiguration接口
-
並且在這個接口提供了一個抽象類: WebSecurity Configurer Adapter
-
WebSecurity Configurer Adapter
//用來配置 用戶簽名服務,主要配置user-details機制,給與用戶賦予角色
//auth 簽名管理器構造器,用於構建用戶具體權限控制
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
this.disableLocalConfigureAuthenticationBldr = true;
}
//用來配置filter鏈
public void configure(WebSecurity web) throws Exception {
}
//配置攔截保護的請求,比如什麼請求放行,什麼請求需要認證
protected void configure(HttpSecurity http) throws Exception {}
-
主要配置filter鏈,配置filtetr鏈 忽略哪些內容
-
WebSecurity Configurer Adapter 提供空實現,也就是沒有任何配置
-
AuthenticationManagerBuilder auth ,定義用戶,密碼,角色
-
HttpSecurity http 用戶 和 角色 與 對應 URL 的訪問權限。如何限定請求權限。
-
滿足通過用戶驗證 或 http基本驗證的任何請求,都會放行。
-
一旦通過驗證,就會得到放行。
自定義用戶信息
-
WebSecurity Configurer Adapter 的方法:
- protected void configure(AuthenticationManagerBuilder auth)
-
security 默認沒有任何用戶配置。
-
boot 如果沒有用戶配置,將默認生成user,密碼隨機
-
內存簽名服務
-
數據庫簽名
-
自定義簽名服務
使用內存簽名服務
-
用戶測試 快速搭建環境
public class TestjApplication extends WebSecurityConfigurerAdapter { public static void main(String[] args) { SpringApplication.run(TestjApplication.class, args); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // 密碼編碼器 PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); // 使用內存存儲 InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> userConfig = auth.inMemoryAuthentication() // 設置密碼編碼器 .passwordEncoder(passwordEncoder); // 註冊用戶admin,密碼爲abc,並賦予USER和ADMIN的角色權限 userConfig.withUser("admin") // 可通過passwordEncoder.encode("abc")得到加密後的密碼 .password("$2a$10$5OpFvQlTIbM9Bx2pfbKVzurdQXL9zndm1SrAjEkPyIuCcZ7CqR6je") .authorities("ROLE_USER", "ROLE_ADMIN"); // 註冊用戶myuser,密碼爲123456,並賦予USER的角色權限 userConfig.withUser("myuser") // 可通過passwordEncoder.encode("123456")得到加密後的密碼 .password("$2a$10$ezW1uns4ZV63FgCLiFHJqOI6oR6jaaPYn33jNrxnkHZ.ayAFmfzLS") .roles("USER"); } }
-
spring 5 的 security 都要密碼編碼器
-
BCryptPasswordEncoder 採用的單項不可逆的密碼,加密方式
-
roles 是 authorities的縮寫。.roles(“USER”) 不用加: “ROLE_USER”
-
and 是連接方法。.and().withUser(“admin”) 配置了一個之後,可以and
- UserDetailsBuilder
- accountExpired 是否過期
- accountLocked 是否鎖定
- credentialsExpired 定義憑證是否過期
- disable 是否禁用用戶
- username 定義用戶名
- authorities 權限
- authorities
- password
- roles 賦予角色,會自動加入前綴 ROLE
使用數據庫定義用戶認證服務
- JdbcUserDetailsManagerConfigurer
創建權限表
//備註:角色的數據是:ROLE_USER ROLE_ADMIN
CREATE TABLE `t_role` (
`id` int(12) NOT NULL,
`role_name` varchar(60) NOT NULL,
`note` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
//備註:用戶表的密碼,明文密文都是可以的
CREATE TABLE `t_user` (
`id` int(12) NOT NULL,
`user_name` varchar(60) DEFAULT NULL,
`pwd` varchar(100) DEFAULT NULL,
`available` int(1) DEFAULT '1',
`note` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `user_name` (`user_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `t_user_role` (
`id` int(12) NOT NULL,
`role_id` int(12) NOT NULL,
`user_id` int(12) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `role_id` (`role_id`,`user_id`),
KEY `user_id` (`user_id`),
CONSTRAINT `t_user_role_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `t_role` (`id`),
CONSTRAINT `t_user_role_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `t_user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
使用數據庫驗證
public class Chapter12Application extends WebSecurityConfigurerAdapter {
// 注入數據源
@Autowired
private DataSource dataSource = null;
// 使用用戶名稱查詢密碼
String pwdQuery = " select user_name, pwd, available" + " from t_user where user_name = ?";
// 使用用戶名稱查詢角色信息
String roleQuery = " select u.user_name, r.role_name " + " from t_user u, t_user_role ur, t_role r "
+ "where u.id = ur.user_id and r.id = ur.role_id" + " and u.user_name = ?";
// 注入數據源
@Autowired
private DataSource dataSource = null;
//對應配置文件:system.user.password.secret=uvwxyz
@Value("${system.user.password.secret}")
private String secret = null;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
PasswordEncoder passwordEncoder = new Pbkdf2PasswordEncoder(this.secret);
auth.jdbcAuthentication()
// 密碼編碼器
.passwordEncoder(passwordEncoder)
// 數據源
.dataSource(dataSource)
// 查詢用戶,自動判斷密碼是否一致
.usersByUsernameQuery(pwdQuery)
// 賦予權限
.authoritiesByUsernameQuery(roleQuery);
}
}
根據用戶名查詢密碼:pwdQuery。返回用戶名 密碼 布爾值(判斷是否有效,1是有效。0無效)
SELECT
user_name,
pwd,
available
FROM
t_user
WHERE
user_name = ''
根據用戶名查詢角色信息:roleQuery
SELECT
u.user_name,
r.role_name
FROM
t_user u,
t_user_role ur,
t_role r
WHERE
u.id = ur.user_id
AND r.id = ur.role_id
AND u.user_name = ''
-
BCrypt 很難破譯,但仍不能避免用戶用123456,截取了這些簡單的密碼,進行匹配
-
通過自己的鑰匙對秒進行加密。
-
//對應配置文件:system.user.password.secret=uvwxyz @Value("${system.user.password.secret}") private String secret = null; PasswordEncoder passwordEncoder = new Pbkdf2PasswordEncoder(this.secret);
-
還有其他的面膜加載器:SCryptPasswordEncoder
-
DelegatingPasswordEncoder
-
自定義用戶認證信息
-
數據庫驗證會造成網絡的緩慢,可用NoSQL
-
redis有,就從redis讀取,如果沒有,就從數據庫查詢用戶信息
-
設置用戶權限 (UserDetailsService.loadUserByUsername 返回UserDetails)
@Service public class UserDetailsServiceImpl implements UserDetailsService { // 注入服務接口 @Autowired private UserRoleService userRoleService = null; @Override @Transactional public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException { // 獲取數據庫用戶信息 DatabaseUser dbUser = userRoleService.getUserByName(userName); // 獲取數據庫角色信息 List<DatabaseRole> roleList = userRoleService.findRolesByUserName(userName); // 將信息轉換爲UserDetails對象 return changeToUser(dbUser, roleList); } private UserDetails changeToUser(DatabaseUser dbUser, List<DatabaseRole> roleList) { // 權限列表 List<GrantedAuthority> authorityList = new ArrayList<>(); // 賦予查詢到的角色 for (DatabaseRole role : roleList) { GrantedAuthority authority = new SimpleGrantedAuthority(role.getRoleName()); authorityList.add(authority); } // 創建UserDetails對象,設置用戶名、密碼和權限 UserDetails userDetails = new User(dbUser.getUserName(), dbUser.getPwd(), authorityList); return userDetails; } }
- 標註@Service
- 注入UserRoleService
- 覆蓋 loadUserByUsername
- UserDetails userDetails = new User
註冊這個認證服務
public class Chapter12Application extends WebSecurityConfigurerAdapter {
@Value("${system.user.password.secret}")
private String secret = null;
@Autowired
private UserDetailsService UserDetailsService = null;
/**
* 覆蓋WebSecurityConfigurerAdapter用戶詳情方法
*
* @param auth
* 用戶簽名管理器構造器
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
PasswordEncoder passwordEncoder = new Pbkdf2PasswordEncoder(this.secret);//設置密碼編碼器
auth.userDetailsService(UserDetailsService).passwordEncoder(passwordEncoder);//設置用戶密碼服務 和 密碼編碼器
}
}
service
public interface UserRoleService {
public DatabaseUser getUserByName(String userName);
public List<DatabaseRole> findRolesByUserName(String userName);
}
@Service
public class UserRoleServiceImpl implements UserRoleService {
@Autowired
private UserDao userDao = null;
@Autowired
private RoleDao roleDao = null;
@Override
@Cacheable(value = "redisCache", key = "'redis_user_'+#userName")
@Transactional
public DatabaseUser getUserByName(String userName) {
return userDao.getUser(userName);
}
@Override
@Cacheable(value = "redisCache", key = "'redis_user_role_'+#userName")
@Transactional
public List<DatabaseRole> findRolesByUserName(String userName) {
return roleDao.findRolesByUserName(userName);
}
}
dao
@Mapper
public interface RoleDao {
public List<DatabaseRole> findRolesByUserName(String userName);
}
@Mapper
public interface UserDao {
public DatabaseUser getUser(String userName);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.springboot.chapter12.dao.RoleDao">
<select id="findRolesByUserName" parameterType="string" resultType="dbrole">
SELECT r.id, r.role_name as roleName, r.note FROM t_role r, t_user_role ur, t_user u
where r.id = ur.role_id and ur.user_id = u.id and u.user_name = #{userName}
</select>
</mapper>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.springboot.chapter12.dao.UserDao">
<select id="getUser" parameterType="string" resultType="dbuser">
SELECT id, user_name as userName, pwd, available, note FROM t_user where user_name = #{userName}
</select>
</mapper>
action
@Controller
public class WelcomeController {
@Autowired
private UserRoleService userRoleService = null;
@GetMapping("/welcome")
public String welcome() {
return "welcome";
}
@GetMapping("/user/details")
@ResponseBody
public DatabaseUser getUser(String userName) {
return userRoleService.getUserByName(userName);
}
@GetMapping("/user/welcome")
public String userWelcome() {
return "welcome";
}
@GetMapping("/admin/welcome")
public String adminWelcome() {
return "welcome";
}
@GetMapping("/admin/welcome1")
public String adminWelcome1() {
return "welcome";
}
@GetMapping("/admin/welcome2")
public String adminWelcome2() {
return "welcome";
}
@GetMapping("/csrf/form")
public String csrfPage() {
return "csrf_form";
}
@PostMapping("/csrf/commit")
@ResponseBody
public Map<String, String> csrfCommit(String name, String describe) {
Map<String, String> map = new HashMap<>();
map.put("name", name);
map.put("describe", describe);
return map;
}
}
限制請求
-
對於不同的角色 訪問的權限也是不一樣的
- 管理員 出了登錄,還需要 不同的角色 賦予 不同的權限
-
extends WebSecurityConfigurerAdapter
- 實現protected void configure(HttpSecurity http)
- 實現對於 不同角色 (用戶) 賦予不同權限的功能
@Override protected void configure(HttpSecurity http) throws Exception { //只需要通過驗證,就能訪問所有請求 http.authorizeRequests().anyRequest().authenticated() .and().formLogin().and().httpBasic();//啓用http基礎 } 只要通過用戶認證,便可訪問所有的請求地址 formLogin 方法 使用security的默認登錄頁面 和 httpBasic方法 啓用瀏覽器的Http基礎認證方式。
配置請求路徑訪問權限(ant風格配置限定)
@Override
protected void configure(HttpSecurity http) throws Exception {
// 限定簽名後的權限
http.
/* ########第一段######## */
authorizeRequests()
// 限定"/user/welcome"請求賦予角色ROLE_USER或者ROLE_ADMIN
.antMatchers("/user/welcome", "/user/details").hasAnyRole("USER", "ADMIN")
// 限定"/admin/"下所有請求權限賦予角色ROLE_ADMIN
.antMatchers("/admin/**").hasAuthority("ROLE_ADMIN")
// 其他路徑允許簽名後訪問
.anyRequest().permitAll()
/* ######## 第二段 ######## */
/** and代表連接詞 **/
// 對於沒有配置權限的其他請求允許匿名訪問
.and().anonymous()
/* ######## 第三段 ######## */
// 使用Spring Security默認的登錄頁面
.and().formLogin()
// 啓動HTTP基礎驗證
.and().httpBasic();
=======================本次結束===============================
}
-
哪些要請求的,將不同的請求賦予不同的請求權限。
-
antMatchers 配置請求路徑
-
hasAnyRole,指定角色
-
.regexMatchers("/admin/.*").hasAuthority(“ROLE_ADMIN”) 通配符 指定
-
有 admin 的權限纔可以訪問
-
.hasAnyRole(“USER”, “ADMIN”) 會默認加 前綴
-
-
.anyRequest().permitAll() 任意都限定的,都可訪問
-
權限方法說明
方法 含義 access(Sring) 參數爲spEL,返回true,則允許訪問 anonymous() 允許匿名訪問 authorizeRequest() 限定通過簽名的請求 anyRequest() 限定任意的請求 hasAnyRole(String …) 將訪問權限賦予多個角色。自動加前綴ROLE_ hasAnyRole(String) 賦予一個角色 permitAll() 無條件允許訪問 and() 連接詞 httpBasic() 啓用瀏覽器的Http基礎驗證 formLogin() 啓用security默認的登錄頁面 not() 對其他方法的訪問採取 取反 fullyAuthenticaticated() 完整驗證,允許訪問(記住我功能不行) denyAll() 無條件不允許任何訪問 hasIpAddress(String) 給定的IP地址,則允許訪問 rememberme() 通過remember-me功能驗證就允許訪問 hasAuthority(String) 給定的角色就允許訪問(不加 ROLE_) hasAuthority(String…) 給定的 任一 角色 - 先配置優先
- 把具體的配置放在前面配置,不具體的放在後面
使用Spring表達式 配合訪問權限
-
access(String) 參數爲SpEL
-
返回true,就是允許訪問。
http.authorizeRequests()
.regexMatchers("/user/welcome", "/user/details")
.hasAnyRole("USER", "ADMIN")
.regexMatchers("/admin/.*")
.hasAuthority("ROLE_ADMIN")
.and().formLogin().and().httpBasic();
http.csrf().disable().authorizeRequests()
// 使用Spring表達式限定只有角色ROLE_USER或者ROLE_ADMIN
.antMatchers("/user/**").access("hasRole('USER') or hasRole('ADMIN')")
// 設置訪問權限給角色ROLE_ADMIN,要求是完整登錄(非記住我登錄)
.antMatchers("/admin/welcome1")
.access("hasAuthority('ROLE_ADMIN') && isFullyAuthenticated()")
// 限定"/admin/welcome2"訪問權限給角色ROLE_ADMIN,允許不完整登錄
.antMatchers("/admin/welcome2").access("hasAuthority('ROLE_ADMIN')")
// 使用記住我的功能
.and().rememberMe()
// 使用Spring Security默認的登錄頁面
.and().formLogin()
// 啓動HTTP基礎驗證
.and().httpBasic();
方法 | 含義 |
---|---|
authentication() | 用戶認證對象 |
denyAll() | 拒絕任何 |
hasAnyRole(String …) | 當前用戶是否存在參數中列明的對象屬性 |
hasRole(String) | 當前用戶是否存在角色 |
hasIpAddress(String) | 是否請求來自指定的ip |
isAnonymous() | 是否匿名訪問 |
isAuthenticated() | 是否通過用戶認證簽名 |
isFullyAuthenticated() | 是否用戶完整驗證(而非記住我) |
isRememberMe() | 是否通過 記住我 功能通過的驗證 |
permitAll() | 無條件允許任何訪問 |
principal() | 用戶的principal對象 |
強制使用 https
- 銀行賬號密碼,通過https協議 採用證書 加密
@Override
protected void configure(HttpSecurity http) throws Exception {
http.
//限定爲https請求
requiresChannel().antMatchers("/admin/**").requiresSecure()
.and()
//user下不使用https請求
.requiresChannel().antMatchers("/user/**").requiresInsecure()
//admin下,需要admin權限
.and().authorizeRequests().antMatchers("/admin/**").hasAnyRole("ADMIN")
//user下,role 和 admin 權限都行
.antMatchers("/user/**").hasAnyRole("ROLE","ADMIN");
}
防止跨站點請求僞造
-
跨站點請求僞造,CSRF cross-site request forgery
-
瀏覽器請求安全網站,登錄後,瀏覽器記錄信息,以Cookie形式保存
-
不關閉瀏覽器的情況下,訪問一個危險網站。
-
危險網站通過獲取 cookie信息來 仿造用戶的請求,進而請求安全網站
-
security 提供 CSRF 過濾器。默認會啓用這個過濾器來防止 CSRF攻擊
-
關閉如下:http.csrf().disable().authorizeRequests() (不建議)
-
不關閉,每次HTTP請求 from 都要求存在 csrf參數
-
當訪問表單,security生成csrf參數,放入表單中
-
當提交表單到服務器時候,就連同csrf參數一併提交
-
security就會對csrf參數判斷。
-
如果禁止,就不會認爲 請求來自 csrf攻擊。
-
這個參數不在cookie中,所以第三方網站無法僞造。
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>CSRF FORM</title>
</head>
<body>
<form action="./commit" method="post">
<p>
名稱:<input id="name" name="name" type="text" value=""/>
</p>
<p>
描述:<input id="describe" name="describe" type="text" value=""/>
</p>
<p>
<input type="submit" value="提交"/>
</p>
<input type="hidden" id="${_csrf.parameterName}"
name="${_csrf.parameterName}" value="${_csrf.token}" />
</form>
</body>
</html>
- _csrf 對象是spring提供的,會放在form的隱藏域中,
- 默認開啓了這個功能,所以要把隱藏的 ${_csrf.parameterName} 標籤放入
- 如果註釋了或者沒有,@PostMapping("/csrf/commit") 無法被請求
用戶認證功能
自定義登錄頁面
@Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().realmName("my-basic-name").and()
// 訪問/admin下的請求需要管理員權限
.authorizeRequests().antMatchers("/admin/**").access("hasRole('ADMIN')")
// 啓用remember me功能
.and().rememberMe().tokenValiditySeconds(86400).key("remember-me-key")
// 啓用HTTP Batic功能
.and().httpBasic()
// 通過簽名後可以訪問任何請求
.and().authorizeRequests().antMatchers("/**").permitAll()
// 設置登錄頁和默認的跳轉路徑
.and().formLogin().loginPage("/login/page").defaultSuccessUrl("/admin/welcome1");
}
-
記住我的有效時間爲1天,86400s
-
cookie 以鍵 remember-me-key保存
-
loginPage 登錄頁面
-
defaultSuccessUrl 默認的成功頁面
-
傳統的映射器映射,或 新增映射關係如下:
@Configuration public class WebConfig implements WebMvcConfigurer { // 增加映射關係 @Override public void addViewControllers(ViewControllerRegistry registry) { // 使得"/login/page"映射爲"login.jsp" registry.addViewController("/login/page").setViewName("login"); // 使得"/logout/page"映射爲"logout_welcome.jsp" registry.addViewController("/logout/page").setViewName("logout_welcome"); // 使得"/logout"映射爲"logout.jsp" registry.addViewController("/logout").setViewName("logout"); } }
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>自定義登錄表單</title> </head> <body> <form action="/login/page" method="POST"> <p>名稱:<input id="username" name="username" type="text" value=""/></p> <p>描述:<input id="password" name="password" type="password" value=""/></p> <p>記住我:<input id="remember_me" name="remember-me" type="checkbox"></p> <p><input type="submit" value="登錄"></p> <input type="hidden" id="${_csrf.parameterName}" name="${_csrf.parameterName}" value="${_csrf.token}"/> </form> </body> </html>
- id="${_csrf.parameterName}" 會自己加
啓用http Basic認證
-
瀏覽器自動彈出簡單的 模態對話框的功能
http.httpBasic().realmName("my-basic-name").
- 啓用httpBasic認證
- realmName 設置模態對話框的標題
登出
-
security提供 /logout
-
使用http 的 post 請求這個URL,就會登出
-
自定義退出
登出之後的顯示。 logout_welcome <body> <h2>您已經登出了系統/h2> </body>
<body>
<form action="/logout/page" method="POST">
<p><input type="submit" value="登出"></p>
<input type="hidden" id="${_csrf.parameterName}"
name="${_csrf.parameterName}" value="${_csrf.token}"/>
</form>
</body>
// 登出頁面和默認跳轉路徑
.and().logout().logoutUrl("/logout/page")
.logoutSuccessUrl("/welcome");*/
Test
PasswordEncoder passwordEncoder = new Pbkdf2PasswordEncoder("uvwxyz");
String p1 = passwordEncoder.encode("abc");
String p2 = passwordEncoder.encode("123456");
//6353063b56655884e216c5f8ff6e405f6ef4e3e1f595c6f69ed088de9e910370aef46ac938979a16
System.out.println(p1);
System.out.println(p2);
//每次生成的都不一樣,但應該都能用
Pbkdf2PasswordEncoder
BCryptPasswordEncoder
SELECT
r.id,
r.role_name AS roleName,
r.note
FROM
t_role r,
t_user_role ur,
t_user u
WHERE
r.id = ur.role_id
AND ur.user_id = u.id
AND u.user_name = 'admin'
10 USER 用戶角色
20 ADMIN 管理員權限
SELECT id, user_name as userName, pwd, available, note FROM t_user where user_name = #{userName}
SELECT
id,
user_name AS userName,
pwd,
available,
note
FROM
t_user
WHERE
user_name = 'admin'
1 admin 6353063b56655884e... 1 管理員備註
redis緩存配置
引入 pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
//還有jsp的兩個包
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<!--不依賴Redis的異步客戶端lettuce -->
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--引入Redis的客戶端驅動jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
配置
#Redis配置
spring.redis.jedis.pool.min-idle=5
spring.redis.jedis.pool.max-active=10
spring.redis.jedis.pool.max-idle=10
spring.redis.jedis.pool.max-wait=2000
spring.redis.port=6379
spring.redis.host=192.168.2.198
#spring.redis.password=123456
spring.cache.type=REDIS
spring.cache.cache-names=redisCache
# 禁用前綴
spring.cache.redis.use-key-prefix=false
# 允許保存空值
#spring.cache.redis.cache-null-values=true
# 自定義前綴
#spring.cache.redis.key-prefix=
# 定義超時時間,單位毫秒
spring.cache.redis.time-to-live=600000
初始化
@Autowired
private RedisTemplate redisTemplate = null;
@PostConstruct
public void initRedisTemplate() {
RedisSerializer<String> strSerializer = redisTemplate.getStringSerializer();
redisTemplate.setKeySerializer(strSerializer);
redisTemplate.setHashKeySerializer(strSerializer);
}
使用
@Service
public class UserRoleServiceImpl implements UserRoleService {
@Autowired
private UserDao userDao = null;
@Autowired
private RoleDao roleDao = null;
@Override
@Cacheable(value = "redisCache", key = "'redis_user_'+#userName")
@Transactional
public DatabaseUser getUserByName(String userName) {
return userDao.getUser(userName);
}
@Override
@Cacheable(value = "redisCache", key = "'redis_user_role_'+#userName")
@Transactional
public List<DatabaseRole> findRolesByUserName(String userName) {
return roleDao.findRolesByUserName(userName);
}
}
jsp
<body>
<form action="/login/page" method="POST">
<p>名稱:<input id="username" name="username" type="text" value=""/></p>
<p>描述:<input id="password" name="password" type="password" value=""/></p>
<p>記住我:<input id="remember_me" name="remember-me" type="checkbox"></p>
<p><input type="submit" value="登錄"></p>
<input type="hidden" id="${_csrf.parameterName}"
name="${_csrf.parameterName}" value="${_csrf.token}"/>
</form>
</body>
<body>
<form action="/logout/page" method="POST">
<p><input type="submit" value="登出"></p>
<input type="hidden" id="${_csrf.parameterName}"
name="${_csrf.parameterName}" value="${_csrf.token}"/>
</form>
</body>