SpringBoot与Spring-Security的集成
Spring Security简介
Spring Security是一个功能强大且可高度自定义的身份验证和访问控制框架。保护基于Spring的应用程序。Spring Security是一个专注于为Java应用程序提供身份验证和授权的框架。与所有Spring项目一样,Spring Security的真正强大之处在于它可以轻松扩展以满足自定义要求
开发文档:
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/
特点:
1.对身份验证和授权的全面和可扩展的支持
2.防止会话固定,点击劫持,跨站点请求伪造等攻击
3.Servlet API集成
4.可与Spring Web MVC集成
与SpringBoot进行集成
引入依赖
<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>
测试接口
@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
:重写里面的方法来设置一些web安全的细节(主要通过重写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)
配置在内存中进行注册公开内存的身份验证
configure(WebSecurity web)
配置拦截资源,例如过滤掉css/js/images
等静态资源
configure(HttpSecurity http)
定义需要拦截的URL
HttpSecurity具体使用可以参考
https://blog.csdn.net/dawangxiong123/article/details/68960041
接下来启动测试下
访问http://localhost:8080/ 没问题,因为配置了antMatchers("/")
访问http://localhost:8080/hello 就提示需要登录
配置基于内存的身份验证
重写configure(AuthenticationManagerBuilder auth)
方法,设置用户名和密码还有角色
@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
强哈希方法来加密密码,如果不加的话登录不上并且控制台会有警告Encoded password does not look like BCrypt
再次访问http://localhost:8080/hello ,输入设置好的用户名和密码
根据角色做接口权限设置
在启动类上加入@EnableGlobalMethodSecurity(prePostEnabled = true)
来实现授权,实现角色对某个操作是否有权限的控制.
@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默认是禁用注解的
1.prePostEnabled:支持Spring EL
表达式,开启后可以使用
@PreAuthorize
:方法执行前的权限验证
@PostAuthorize
:方法执行后再进行权限验证
@PreFilter
:方法执行前对集合类型的参数或返回值进行过滤,移除使对应表达式的结果为false的元素
@PostFilter
:方法执行后对集合类型的参数或返回值进行过滤,移除使对应表达式的结果为false的元素
2.secureEnabled : 开启后可以使用
@Secured
:用来定义业务方法的安全性配置属性列表
3.jsr250Enabled :支持JSR
标准,开启后可以使用
@RolesAllowed
:对方法进行角色验证
@DenyAll
:允许所有角色调用
@PermitAll
:不允许允许角色调用
controller层做相应的配置
@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')")
:访问前对角色做校验,只有ADMIN的角色才能访问
Spring-Security基于表达式的权限控制
Spring Security允许我们在定义URL访问或方法访问所应有的权限时使用Spring EL表达式
Spring Security可用表达式对象的基类是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;
}
}
}
角色默认前缀是ROLE_
hasAuthority([auth])
:等同于hasRole
hasAnyAuthority([auth1,auth2])
:等同于hasAnyRole
hasRole([role])
:当前用户是否拥有指定角色。
hasAnyRole([role1,role2])
:多个角色是一个以逗号进行分隔的字符串。如果当前用户拥有指定角色中的任意一个则返回true
Principle
:代表当前用户的principle
对象
authentication
:直接从SecurityContext
获取的当前Authentication
对象
permitAll()
:总是返回true
,表示允许所有的
denyAll()
:总是返回false
,表示拒绝所有的
isAnonymous()
:当前用户是否是一个匿名用户
isAuthenticated()
:表示当前用户是否已经登录认证成功了
isRememberMe()
:表示当前用户是否是通过Remember-Me
自动登录的
isFullyAuthenticated()
:如果当前用户既不是一个匿名用户,同时又不是通过Remember-Me
自动登录的,则返回true
hasPermission()
:当前用户是否拥有指定权限
更多关于Spring-Security基于表达式的权限控制可以参考:
https://my.oschina.net/liuyuantao/blog/1924776
访问http://localhost:8080/roleAuth接口时只有ADMIN角色可以访问,其他角色访问会报Forbidden403
注:Spring-Security严格区分大小写
总结:
Spring-Security的角色权限验证主要就是用到hasRole()
和hasPermission()
前后端分离下,需要在接口上面做认证
具体实现参考:https://blog.csdn.net/cloume/article/details/83790111
前后端不分离的话,需要在前端展示页面上做下角色权限校验设置,原理都一样
具体实现参考:https://www.jianshu.com/p/155ec4272aa4