深入boot2.0 12章 spring security

我的整理:
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 (int1的標識,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>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章