Spring Security簡(jiǎn)介
Spring Security是一個(gè)功能強(qiáng)大且可高度自定義的身份驗(yàn)證和訪問(wèn)控制框架切厘。保護(hù)基于Spring的應(yīng)用程序纷妆。Spring Security是一個(gè)專注于為Java應(yīng)用程序提供身份驗(yàn)證和授權(quán)的框架铺呵。與所有Spring項(xiàng)目一樣崖蜜,Spring Security的真正強(qiáng)大之處在于它可以輕松擴(kuò)展以滿足自定義要求
Github地址:https://github.com/spring-projects/spring-security
開發(fā)文檔:
https://docs.spring.io/spring-security/site/docs/5.1.2.RELEASE/reference/htmlsingle/
API文檔:
https://docs.spring.io/spring-security/site/docs/5.1.2.RELEASE/api/
特點(diǎn):
1.對(duì)身份驗(yàn)證和授權(quán)的全面和可擴(kuò)展的支持
2.防止會(huì)話固定己儒,點(diǎn)擊劫持誓军,跨站點(diǎn)請(qǐng)求偽造等攻擊
3.Servlet API集成
4.可與Spring Web MVC集成
與SpringBoot進(jìn)行集成
引入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
測(cè)試接口
@RestController
public class controller {
@RequestMapping("/")
public String home() {
return "hello spring boot";
}
@RequestMapping("/hello")
public String hello() {
return "hello world";
}
}
開啟提供基于web的Security配置
@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/").permitAll()
.anyRequest().authenticated()
.and()
.logout().permitAll()
.and()
.formLogin();
http.csrf().disable();
}
}
@EnableWebMvcSecurity
:開啟Spring Security的功能
WebSecurityConfigurerAdapter
:重寫里面的方法來(lái)設(shè)置一些web安全的細(xì)節(jié)(主要通過(guò)重寫configure()
)
WebSecurityConfigurerAdapter
中configure
方法的源碼
public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigurer<WebSecurity> {
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
this.disableLocalConfigureAuthenticationBldr = true;
}
public void configure(WebSecurity web) throws Exception {
}
protected void configure(HttpSecurity http) throws Exception {
this.logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
((HttpSecurity)((HttpSecurity)((AuthorizedUrl)http.authorizeRequests().anyRequest()).authenticated().and()).formLogin().and()).httpBasic();
}
......
}
configure(AuthenticationManagerBuilder auth)
配置在內(nèi)存中進(jìn)行注冊(cè)公開內(nèi)存的身份驗(yàn)證
configure(WebSecurity web)
配置攔截資源,例如過(guò)濾掉css/js/images
等靜態(tài)資源
configure(HttpSecurity http)
定義需要攔截的URL
HttpSecurity具體使用可以參考
https://blog.csdn.net/dawangxiong123/article/details/68960041
接下來(lái)啟動(dòng)測(cè)試下
訪問(wèn)http://localhost:8080/ 沒問(wèn)題同蜻,因?yàn)榕渲昧?code>antMatchers("/")
訪問(wèn)http://localhost:8080/hello 就提示需要登錄
配置基于內(nèi)存的身份驗(yàn)證
重寫configure(AuthenticationManagerBuilder auth)
方法棚点,設(shè)置用戶名和密碼還有角色
@Bean
public BCryptPasswordEncoder PasswordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.password(PasswordEncoder().encode("123456"))
.roles("ADMIN");
}
注:SpringBoot2.x后需要使用BCrypt
強(qiáng)哈希方法來(lái)加密密碼,如果不加的話登錄不上并且控制臺(tái)會(huì)有警告Encoded password does not look like BCrypt
再次訪問(wèn)http://localhost:8080/hello 湾蔓,輸入設(shè)置好的用戶名和密碼
根據(jù)角色做接口權(quán)限設(shè)置
在啟動(dòng)類上加入@EnableGlobalMethodSecurity(prePostEnabled = true)
來(lái)實(shí)現(xiàn)授權(quán),實(shí)現(xiàn)角色對(duì)某個(gè)操作是否有權(quán)限的控制.
@EnableGlobalMethodSecurity源碼
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({GlobalMethodSecuritySelector.class})
@EnableGlobalAuthentication
@Configuration
public @interface EnableGlobalMethodSecurity {
boolean prePostEnabled() default false;
boolean securedEnabled() default false;
boolean jsr250Enabled() default false;
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default 2147483647;
}
Spring Security默認(rèn)是禁用注解的
1.prePostEnabled:支持Spring EL
表達(dá)式瘫析,開啟后可以使用
@PreAuthorize
:方法執(zhí)行前的權(quán)限驗(yàn)證
@PostAuthorize
:方法執(zhí)行后再進(jìn)行權(quán)限驗(yàn)證
@PreFilter
:方法執(zhí)行前對(duì)集合類型的參數(shù)或返回值進(jìn)行過(guò)濾,移除使對(duì)應(yīng)表達(dá)式的結(jié)果為false的元素
@PostFilter
:方法執(zhí)行后對(duì)集合類型的參數(shù)或返回值進(jìn)行過(guò)濾默责,移除使對(duì)應(yīng)表達(dá)式的結(jié)果為false的元素
2.secureEnabled : 開啟后可以使用
@Secured
:用來(lái)定義業(yè)務(wù)方法的安全性配置屬性列表
3.jsr250Enabled :支持JSR
標(biāo)準(zhǔn)贬循,開啟后可以使用
@RolesAllowed
:對(duì)方法進(jìn)行角色驗(yàn)證
@DenyAll
:允許所有角色調(diào)用
@PermitAll
:不允許允許角色調(diào)用
controller層做相應(yīng)的配置
@RestController
public class controller {
@RequestMapping("/")
public String home() {
return "hello spring boot";
}
@RequestMapping("/hello")
public String hello() {
return "hello world";
}
@PreAuthorize("hasRole('ROLE_ADMIN')")
@RequestMapping("/roleAuth")
public String role() {
return "admin auth";
}
}
@PreAuthorize("hasRole('ROLE_ADMIN')")
:訪問(wèn)前對(duì)角色做校驗(yàn),只有ADMIN的角色才能訪問(wèn)
Spring-Security基于表達(dá)式的權(quán)限控制
Spring Security允許我們?cè)诙xURL訪問(wèn)或方法訪問(wèn)所應(yīng)有的權(quán)限時(shí)使用Spring EL
表達(dá)式
Spring Security可用表達(dá)式對(duì)象的基類是SecurityExpressionRoot
SecurityExpressionRoot源碼
public abstract class SecurityExpressionRoot implements SecurityExpressionOperations {
protected final Authentication authentication;
private AuthenticationTrustResolver trustResolver;
private RoleHierarchy roleHierarchy;
private Set<String> roles;
private String defaultRolePrefix = "ROLE_";
public final boolean permitAll = true;
public final boolean denyAll = false;
private PermissionEvaluator permissionEvaluator;
public final String read = "read";
public final String write = "write";
public final String create = "create";
public final String delete = "delete";
public final String admin = "administration";
public SecurityExpressionRoot(Authentication authentication) {
if (authentication == null) {
throw new IllegalArgumentException("Authentication object cannot be null");
} else {
this.authentication = authentication;
}
}
public final boolean hasAuthority(String authority) {
return this.hasAnyAuthority(authority);
}
public final boolean hasAnyAuthority(String... authorities) {
return this.hasAnyAuthorityName((String)null, authorities);
}
public final boolean hasRole(String role) {
return this.hasAnyRole(role);
}
public final boolean hasAnyRole(String... roles) {
return this.hasAnyAuthorityName(this.defaultRolePrefix, roles);
}
private boolean hasAnyAuthorityName(String prefix, String... roles) {
Set<String> roleSet = this.getAuthoritySet();
String[] var4 = roles;
int var5 = roles.length;
for(int var6 = 0; var6 < var5; ++var6) {
String role = var4[var6];
String defaultedRole = getRoleWithDefaultPrefix(prefix, role);
if (roleSet.contains(defaultedRole)) {
return true;
}
}
return false;
}
public final Authentication getAuthentication() {
return this.authentication;
}
public final boolean permitAll() {
return true;
}
public final boolean denyAll() {
return false;
}
public final boolean isAnonymous() {
return this.trustResolver.isAnonymous(this.authentication);
}
public final boolean isAuthenticated() {
return !this.isAnonymous();
}
public final boolean isRememberMe() {
return this.trustResolver.isRememberMe(this.authentication);
}
public final boolean isFullyAuthenticated() {
return !this.trustResolver.isAnonymous(this.authentication) && !this.trustResolver.isRememberMe(this.authentication);
}
public Object getPrincipal() {
return this.authentication.getPrincipal();
}
public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
this.trustResolver = trustResolver;
}
public void setRoleHierarchy(RoleHierarchy roleHierarchy) {
this.roleHierarchy = roleHierarchy;
}
public void setDefaultRolePrefix(String defaultRolePrefix) {
this.defaultRolePrefix = defaultRolePrefix;
}
private Set<String> getAuthoritySet() {
if (this.roles == null) {
this.roles = new HashSet();
Collection<? extends GrantedAuthority> userAuthorities = this.authentication.getAuthorities();
if (this.roleHierarchy != null) {
userAuthorities = this.roleHierarchy.getReachableGrantedAuthorities(userAuthorities);
}
this.roles = AuthorityUtils.authorityListToSet(userAuthorities);
}
return this.roles;
}
public boolean hasPermission(Object target, Object permission) {
return this.permissionEvaluator.hasPermission(this.authentication, target, permission);
}
public boolean hasPermission(Object targetId, String targetType, Object permission) {
return this.permissionEvaluator.hasPermission(this.authentication, (Serializable)targetId, targetType, permission);
}
public void setPermissionEvaluator(PermissionEvaluator permissionEvaluator) {
this.permissionEvaluator = permissionEvaluator;
}
private static String getRoleWithDefaultPrefix(String defaultRolePrefix, String role) {
if (role == null) {
return role;
} else if (defaultRolePrefix != null && defaultRolePrefix.length() != 0) {
return role.startsWith(defaultRolePrefix) ? role : defaultRolePrefix + role;
} else {
return role;
}
}
}
角色默認(rèn)前綴是ROLE_
hasAuthority([auth])
:等同于hasRole
hasAnyAuthority([auth1,auth2])
:等同于hasAnyRole
hasRole([role])
:當(dāng)前用戶是否擁有指定角色桃序。
hasAnyRole([role1,role2])
:多個(gè)角色是一個(gè)以逗號(hào)進(jìn)行分隔的字符串杖虾。如果當(dāng)前用戶擁有指定角色中的任意一個(gè)則返回true
Principle
:代表當(dāng)前用戶的principle
對(duì)象
authentication
:直接從SecurityContext
獲取的當(dāng)前Authentication
對(duì)象
permitAll()
:總是返回true
,表示允許所有的
denyAll()
:總是返回false
媒熊,表示拒絕所有的
isAnonymous()
:當(dāng)前用戶是否是一個(gè)匿名用戶
isAuthenticated()
:表示當(dāng)前用戶是否已經(jīng)登錄認(rèn)證成功了
isRememberMe()
:表示當(dāng)前用戶是否是通過(guò)Remember-Me
自動(dòng)登錄的
isFullyAuthenticated()
:如果當(dāng)前用戶既不是一個(gè)匿名用戶奇适,同時(shí)又不是通過(guò)Remember-Me
自動(dòng)登錄的,則返回true
hasPermission()
:當(dāng)前用戶是否擁有指定權(quán)限
更多關(guān)于Spring-Security基于表達(dá)式的權(quán)限控制可以參考:
https://my.oschina.net/liuyuantao/blog/1924776
訪問(wèn)http://localhost:8080/roleAuth接口時(shí)只有ADMIN角色可以訪問(wèn)芦鳍,其他角色訪問(wèn)會(huì)報(bào)Forbidden403
注:Spring-Security嚴(yán)格區(qū)分大小寫
總結(jié):
Spring-Security的角色權(quán)限驗(yàn)證主要就是用到hasRole()
和hasPermission()
前后端分離下嚷往,需要在接口上面做認(rèn)證
具體實(shí)現(xiàn)參考:https://blog.csdn.net/cloume/article/details/83790111
前后端不分離的話,需要在前端展示頁(yè)面上做下角色權(quán)限校驗(yàn)設(shè)置柠衅,原理都一樣
具體實(shí)現(xiàn)參考:http://www.reibang.com/p/155ec4272aa4