目录

开始之前

快速开始

使用内存签名服务

使用数据库签名服务

使用自定义签名服务

限制请求

强制使用HTTPS

防止跨站点伪造请求

用户认证功能


在java web工程中,一般使用Servlet过滤器(Filter)对于请求进行拦截,然后在Filter中通过自己的验证逻辑来决定是否放行请求。基于这一原理,常用的SpringMVC实现了自己的拦截器,同样的,Spring Security也是基于这个原理,在进入到DispatcherServlet前就可以对Spring MVC的请求进行拦截,然后通过一定的验证(一般验证用户是否有某个权限、请求类型、请求方式等),从而决定是否放行某个请求。

开始之前

1、Spring Security的主要功能就是通过一定的验证,从而决定是否放行某个请求,可以实现用户访问权限控制,HTTP和HTTPS访问控制、CSRF(跨站点请求伪造)访问控制等;

2、Spring Security的拦截会默认先于其它过滤器之前执行;

3、针对Spring Security,角色权限的命名强制都以"ROLE_"开头且全部大写,比如"ROLE_USER","ROLE_ADMIN",

"ROLE_DBA",当然,角色权限可以任意命名,甚至可以定义一个角色权限为"ROLE_HAHA","ROLE_WUDI"。其提供的方法有的会自动给角色权限加上"ROLE_",这时,就不能给角色权限加上"ROLE_",这里需要注意(后面会提供说明)。

快速开始

1、在Spring Boot项目中引入Spring Security的依赖。

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>

2、启动注解

官方定义的是:web工程使用@EnableWebSecurity,非web工程可以使用@EnableGlobalAuthentication。而事实上,@EnableWebSecurity已经标注了@EnableGlobalAuthentication(可以点进源码查看)。

所以,只需要在启动类上标注@EnableWebSecurity即可开启Spring Security功能。

3、启动Spring Boot项目

启动Spring Boot项目后,可以在Console里面看到随机生成的秘钥,如下。

随即在浏览器中任意输入一个存在于Spring Boot项目中的url,就会出现一个拦截页面,输入刚刚生成的秘钥即可访问到预期的页面,如下。

4、短板

上述过程暴露了一下问题:

-每次启动都会生成不同的秘钥,造成在访问的过程每次都要输入不同的秘钥,如果丢失又要重启,实在是不方便;

-用户只能使用‘user’账号,无法多样化,不适合构建不同的权限;

-不能自定义自己的验证方式和策略;

-验证界面不美观;

-不能定义哪些url需要验证,哪些不需要;

.....................

为了克服只能使用user+自动生成的秘钥引起的弊端,Spring Security提供了使用内存签名服务、数据库签名服务和自定义签名服务。

使用内存签名服务

顾名思义,就是将用户信息存放在内存中(实际项目都是在数据库中)。相对而言,它比较简单,适合于快速搭建测试环境。

1、继承WebSecurityConfigurerAdapter重写拦截配置

里面有三个常用方法供我们重写,如下。

@Configuration
public class RoleConfig extends WebSecurityConfigurerAdapter {/*** <p>用来配置用户签名服务,主要是user-detail机制,还可以给与用户赋予角色* */@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {// TODO Auto-generated method stubsuper.configure(auth);}/*** <p>用来配置Filter链* */@Overridepublic void configure(WebSecurity web) throws Exception {// TODO Auto-generated method stubsuper.configure(web);}/*** <p>用来配置拦截保护请求,比如什么请求放行,什么请求需要验证* */@Overrideprotected void configure(HttpSecurity http) throws Exception {// TODO Auto-generated method stubsuper.configure(http);}
}

2、定义签名服务

根据上面所述,我们只需要重写下面的方法即可。

/*** <p>用来配置用户签名服务,主要是user-detail机制,还可以给与用户赋予角色* */@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {// TODO Auto-generated method stubsuper.configure(auth);}

完整的示例

@Configuration
public class RoleConfig extends WebSecurityConfigurerAdapter {/*** <p>用来配置用户签名服务,主要是user-detail机制,还可以给与用户赋予角色* */@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//密码编码器-在Spring5的Security中都要求使用密码编码器,否则会发生异常BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();//使用内存签名服务auth.inMemoryAuthentication()//设置密码编码器.passwordEncoder(passwordEncoder)//注册用户admin,密码为abc123,并且赋予USER和ADMIN的角色权限 .withUser("admin").password(passwordEncoder.encode("abc123")).roles("USER", "ADMIN")//连接方法and().and()//注册用户myuser,密码为123456,并且赋予USER角色权限 .withUser("myuser").password(passwordEncoder.encode("123456")).roles("USER");//还需要注册其它用户继续用and()连接......}
}

3、重新启动Spring Boot项目

可以发现Console中不再为我们打印秘钥了,随即在浏览器中输入任意一个Controller中存在的url,同样出现拦截页面,输入我们自定义注册的用户即可访问到预期的页面。

4、其它

user-detail机制其它构造方法

方法 描述
accountExpired(boolean accountExpired) 设置账号是否过期
accountLocked(boolean accountLocked) 是否锁定账号
credentialsExpired(boolean credentialsExpired) 定义凭证是否过期
disabled(boolean disabled) 是否禁用用户
username(String username) 定义用户名,不能为null
authorities(GrantedAuthority... authorities) 赋予一个或多个权限,需要加上ROLE_
authorities(List<? extends GrantedAuthority> authorities) 使用列表(List)赋予权限
password(String password) 定义密码
roles(String... roles) 赋予一个或多个权限,会自动加上ROLE_

提示:上面示例已经演示了username()、password()和roles()构造方法了,其它的依葫芦画瓢即可。

使用数据库签名服务

毕竟内存有限,且也不适合实际开发环境。因此Spring Security提供了对数据库的查询方法来满足需求。

1、创建数如下据表,并配置好数据源等其他配置

说明:user_available字段表示用户是否可用1-可用,0-不可用。

2、继承WebSecurityConfigurerAdapter重写拦截配置,定义签名服务

@Configuration
public class RoleConfig2 extends WebSecurityConfigurerAdapter {//注入数据源@Autowiredprivate DataSource dataSource;//根据用户名查询用户信息private final String getUserByUsername = "SELECT user_name as username, user_pwd as password, user_available as enabled "+ "FROM tb_user "+ "WHERE user_name = ?";//根据用户名查询角色信息private final String getRoleByUsername = "SELECT u.user_name as username, r.role_name as authority "+ "FROM tb_user u, tb_user_role_mid ur, tb_role r "+ "WHERE u.user_id = ur.user_id AND r.role_id = ur.role_id AND u.user_name = ?";/*** <p>用来配置用户签名服务,主要是user-detail机制,还可以给与用户赋予角色* */@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//密码编码器-在Spring5的Security中都要求使用密码编码器,否则会发生异常BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();//使用数据库签名服务auth.jdbcAuthentication()//设置密码编码器.passwordEncoder(passwordEncoder)//数据源.dataSource(dataSource)//查询用户,自动判断密码是否一致.usersByUsernameQuery(getUserByUsername)//赋予权限.authoritiesByUsernameQuery(getRoleByUsername);}
}

3、重新启动Spring Boot项目

可以发现Console中不再为我们打印秘钥了,随即在浏览器中输入任意一个Controller中存在的url,同样出现拦截页面,输入我们自定义注册的用户即可访问到预期的页面。

使用自定义签名服务

接着使用上面的数据库表。

1、实现UserDetaisService接口定义签名服务

//这里直接标记为@Service,作为bean扫描进IoC,省去bean的单独配置
@Service
public class UserDetailsServiceImpl implements UserDetailsService {//普通点的dao层接口,获取相应的用户数据和角色权限@Autowiredprivate UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//获取数据库用户信息RoleUser user = userMapper.getUserByName(username);//获取数据库角色信息List<Role> roles = userMapper.listRolesByUserName(username);//将信息转换为UserDetails对象//权限列表List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();//赋予查询到的角色for(Role role : roles) {SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role.getRoleName());authorities.add(authority);}//创建UserDetails对象,设置用户名、密码和权限  这里的编码器要保持一致BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();UserDetails userDetails = new User(user.getUserName(), passwordEncoder.encode(user.getUserPwd()), authorities);return userDetails;}
}
@Configuration
public class RoleConfig3 extends WebSecurityConfigurerAdapter {@Autowiredprivate UserDetailsService userDetailsService;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//密码编码器-在Spring5的Security中都要求使用密码编码器,否则会发生异常BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);}
}

2、重新启动Spring Boot项目

可以发现Console中不再为我们打印秘钥了,随即在浏览器中输入任意一个Controller中存在的url,同样出现拦截页面,输入我们自定义注册的用户即可访问到预期的页面。

3、小结

可以发现,实现UserDetailsService这种方式最灵活,用户数据不仅可以来源于数据库,还可以在数据库压力大的情况下转而访问缓存获取用户数据。

限制请求

上面只是实现了验证用户,并且赋予了用户某些角色权限,任何技术如果不是用于解决生产问题,那就是耍流氓。

前面说到,继承WebSecurityConfigurerAdapter 经常使用的三个需要重新方法是

@Configuration
public class RoleConfig extends WebSecurityConfigurerAdapter {/*** <p>用来配置用户签名服务,主要是user-detail机制,还可以给与用户赋予角色* */@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {// TODO Auto-generated method stubsuper.configure(auth);}/*** <p>用来配置Filter链* */@Overridepublic void configure(WebSecurity web) throws Exception {// TODO Auto-generated method stubsuper.configure(web);}/*** <p>用来配置拦截保护请求,比如什么请求放行,什么请求需要验证* */@Overrideprotected void configure(HttpSecurity http) throws Exception {// TODO Auto-generated method stubsuper.configure(http);}
}

前面重写了configure(AuthenticationManagerBuilder auth)实现了用户验证和赋予用户角色权限。接下来,就需要重写configure(HttpSecurity http)实现限制请求,如下。

1、重写configure(HttpSecurity http)方法

@Overrideprotected 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/**").hasAnyAuthority("ROLE_ADMIN")//其它路径允许签名后访问.anyRequest().permitAll()/**第二段**///对于没有配置权限的其它请求允许匿名访问.and().anonymous()//使用Spring Security的默认登录页面.and().formLogin()//HTTP基础验证.and().httpBasic();}

2、现身说法

上面的配置,拥有ADMIN权限的可以访问"/admin/**"路径下的所有,拥有USER权限或ADMIN的可以访问"/user/welcome"和"/user/details",其它路径可以匿名访问。

对于这里的限制请求配置,明显产生了权限冲突,针对此问题,Spring Security采取了配置优先原则来解决,比如上面第二段允许匿名的访问,且没有给出uri地址,但是第一段中加入了限制,还是会采取第一段的限制访问。

因此,生产中,需要把具体的配置防止前面,把不具体的配置放到后面。

还应该注意方法上是否需要加上"ROLE_"的区别。

另外,除了使用上面的Ant风格编码,还可以使用正则规则,例如

http.authorizeRequests().regexMatchers("/user/welcome", "/user/details").hasAnyRole("USER", "ADMIN").regexMatchers("/admin/.*").hasAnyAuthority("ROLE_ADMIN").and().formLogin().and().httpBasic();

3、其它权限方法说明

方法 说明
access(String) 参数为Spring EL,如果返回true则允许访问,需要配合Spring EL表达式使用
anonymous() 允许匿名访问
authorizeRequests() 限定通过签名的请求
anyRequest() 限定任意的请求
hasAnyRole(String ...) 将访问权限赋予多个角色(角色会自动加入前缀"ROLE_")
hasRole(String) 将访问权限赋予一个角色(角色会自动加入前缀"ROLE_")
permitAll() 无条件允许访问
and() 连接词,并取消之前限定的前提规则
httpBasic() 启用浏览器的HTTP基础验证
formLogin() 启用Spring Security默认的登录页面
not() 对其它方法的访问采取求反
fullyAuthorized() 如果是完整验证(并发Remember-me),则允许访问
denyAll() 无条件不允许任何访问
hasIpAddress(String) 如果是给定的IP地址则允许访问
rememberme() 用户通过Remember-me功能验证就允许访问
hasAnyAuthority(String ...) 如果是给定的多个角色就允许访问(角色不会自动加入前缀"ROLE_")
hasAuthority(String) 如果是给定的一个角色就允许访问(角色不会自动加入前缀"ROLE_")

4、使用Spring EL表达式配置访问权限(等价于Ant风格配置)

@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()//使用Spring EL 表达式现在只有角色ROLE_USER或者ADMIN.antMatchers("/user/**").access("hasRole('USER') or hasRole('ADMIN')")//设置访问权限给角色ADMIN,要求是完整登录(非记住我登录).antMatchers("/admin/welcome1").access("hasAuthority('ROLE_ADMIN') && isFullyAuthenticated()")//设置访问权限给角色ADMIN,允许使用非完整登录(使用记住我登录).antMatchers("/admin/welcome2").access("hasAuthority('ROLE_ADMIN')")//使用记住我功能.and().rememberMe()//Spring Security默认登录页面.and().formLogin()//http基础验证.and().httpBasic();
}

上面的代码中,在access()方法里用了3个Spring表达式,除此之外,Spring Security还提供了以下表达式方法:

方法 说明
authentication() 用户认证对象
denyAll() 拒绝任何访问
hasAnyRole(String ...) 赋予一个或多个角色权限,会自动加上"ROLE_"
hasRole(String) 赋予一个角色权限,会自动加上"ROLE_"
hasIpAdress(String) 是否请求来自指定IP
isAnonymous() 是否匿名访问
isAuthenticated() 是否用户通过认证签名
isFullyAuthenticated() 限制完整登录(非记住我功能)
isRememberMe() 是否通过"记住我"功能通过验证
hasAuthority(String) 赋予一个角色权限,需要手动加上"ROLE_"
hasAnyAuthority(String ...) 赋予一个或多个角色权限,需要手动加上"ROLE_"
permitAll() 无条件允许任何访问
principal() 用户的principal对象

强制使用HTTPS

1、简单的示例

@Overrideprotected void configure(HttpSecurity http) throws Exception {http//使用安全渠道,强制使用https.requiresChannel().antMatchers("/admin/**").requiresSecure().and()//不使用https.requiresChannel().antMatchers("/user/**").requiresInsecure().and()//限定角色访问权限.authorizeRequests().antMatchers("/admin/**").hasAnyRole("ADMIN").antMatchers("/user/**").hasAnyRole("ROLE", "ADMIN");
}

2、现身说法

这里的requiresChannel()方法说明使用通道,然后antMatchers()是一个限定请求,然后requiresSecure()表示使用HTTPS请求,而requiresInsecure()则是取消安全请求的限制,这样就可以使用普通的HTTP。

防止跨站点伪造请求

Spring Security在默认的情况下,是已经开启了防止CSRF攻击的过滤器,如果需要关闭(当然是不建议),可以这么做:

http.csrf().disable().authorizeRequests()...

Spring Security会为每次需要提交的表单提供一个key-value形式的参数,这个信息不存在Cookie中,所以无法伪造,然后根据客户端传递的参数和服务端比较,正确才会放行。

用户认证功能

1、自定义登录页面并使用"记住我"功能

简单的示例

http//设置角色访问权限.authorizeRequests().antMatchers("/admin/**").hasRole("ADMIN").and()//启用remember me功能.rememberMe().tokenValiditySeconds(86400).key("remember-me-key").and()//启用HTTP Basic功能.httpBasic().and()//通过签名后可以访问任何路径.authorizeRequests().antMatchers("/**").permitAll().and()//设置默认的登录页和跳转路径.formLogin().loginPage("/login/page").defaultSuccessUrl("/admin/welcome1");

现身说法

这里的 rememberMe()方法就是启用"记住我"功能,有效时间为86400s(即1天),在浏览器中以Cookie存储,键"remember-me-key"。loginPage()方法设置默认的登录页,defaultSuccessUrl()方法设置默认的跳转路径。

这里的"/login/page"所映射的路径,可以使用传统的Controller控制层去映射,也可以使用新增映射关系去完成,如下:

@Configuration
public class WebConfig implements WebMvcConfigurer {/*** <p>新增映射关系* */@Overridepublic void addViewControllers(ViewControllerRegistry registry) {//使得/login/page映射为login.jspregistry.addViewController("/login/page").setViewName("login");//使得/logout/page映射为logout_welcome.jspregistry.addViewController("/logout/page").setViewName("logout_welcome");//使得/logout映射为logout.jspregistry.addViewController("/logout").setViewName("logout");}
}

注意

登录页面的参数名必须是账号:username、密码:password、记住我:remember-me,且记住我为一个checkbox,这样Spring Security才能获取这些参数,且提交方式必须为POST。

2、启用HTTP Basic认证

在前面调用httpBasic()方法就是启动了HTTP Basic功能。还可以调用realmName(String)方法为认证设置模态对话框的标题。

3、登出

默认情况下,Spring Security会提供一个URI--"/logout",用POST请求了这个uri,Spring Security就会推出,且清楚remember me的相关信息。仿造自定义登录,也可以实现自定义推出。

Spring Boot实践 | 利用Spring Security快速搞定权限控制相关推荐

  1. Spring Boot + Vue + Shiro 实现前后端分离、权限控制

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 来源:http://sina.lt/gauR 本文总结自实习中对项 ...

  2. Spring Boot + SpringSecurity + JWT 实现简单的 restful Api 权限控制

    对于前后端分离的项目,后端对于接口访问的权限控制是必须要做的,也就是需要根据用户的权限进行控制,这样才能对我们的接口资源进行一定程度的保护,在一个web项目中,我们的通常做法是,允许登录后的用户进行接 ...

  3. Spring Boot Web应用集成Keycloak进阶之细粒度权限控制

    [](()资源管理 ======================================================================= 资源管理主要用来定义资源服务器的哪些 ...

  4. Spring Boot + Vue + Shiro 实现前后端分离、权限控制 (附源码)

    点击上方[全栈开发者社区]→右上角[...]→[设为星标⭐] 原项目采用Springboot+freemarker模版,开发过程中觉得前端逻辑写的实在恶心,后端Controller层还必须返回Free ...

  5. Spring Boot 实践折腾记(11):使用 Spring 5的WebFlux快速构建效响应式REST API

    关于Spring 5中的反应式编程支持Reactor类库,上一篇文章< Spring Boot 实践折腾记(10):2.0+版本中的反应式编程支持--Reactor>已经简要介绍过,Spr ...

  6. Spring Boot实践——Spring AOP实现之动态代理

    Spring AOP 介绍 AOP的介绍可以查看 Spring Boot实践--AOP实现 与AspectJ的静态代理不同,Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改 ...

  7. Spring boot中使用Spring Security的记住我 remember-me功能

    Spring boot中使用Spring Security的记住我 remember-me功能 问题描述:Spring security新手,在登录时加上记住我功能,需要使用框架自带的记住我. 记住的 ...

  8. Spring Boot实践

    Spring Boot实践 在本文中,我将重点介绍Spring Boot特有的实践(大多数时候,也适用于Spring项目).以下依次列出了最佳实践,排名不分先后. 1.使用自定义BOM来维护第三方依赖 ...

  9. Spring Boot 实践折腾记(12):支持数据缓存Cache

    不管是什么类型的应用程序,都离不开数据,即便如现在的手机APP,我们依然需要使用数数据库,对于不懂的人,当然,我们可以告诉他们一些高大上的概念,但是作为专业人士,就一定要明白背后的真实原理到底是什么. ...

最新文章

  1. VMware Server 2.0简单学习!
  2. eclipse下classes文件夹无法发布到tomcat的问题--tomcat发布慢的问题
  3. LoadRunner解决超时错误
  4. 门限的限意思是什么_门限是什么意思_门限英文翻译
  5. 值得收藏的Python小技巧:这17个骚操作你都OK吗?
  6. stackexchange_通过Spring Social推特StackExchange –第1部分
  7. Redis五种数据类型及应用场景
  8. vs各个版本的编译器号
  9. MacBook Pro M1 安装 VMware Fusion 及 CentOS 8
  10. vscode安装使用教程
  11. 好玩的猜数游戏(不是二分查找!四位数)
  12. 小红书种草笔记怎么写?种草笔记标题怎么写比较好
  13. 信创办公--基于WPS的Word最佳实践系列(使用智能图形丰富表达内容)
  14. Python处理视频文件的实用姿势
  15. javaweb项目实战(附有源码)
  16. android tools ignore,android tools属性引用
  17. 匹兹堡大学申请条件计算机科学,匹兹堡大学工程学院计算机科学专业申请条件...
  18. LANL Earthquake Prediction收获
  19. css父div半透明子div不透明
  20. BIM建模师考试试题及答案

热门文章

  1. 搜索效果和搜索动画效果
  2. 升级IOS百度人脸SDK4.0采坑记录
  3. 15000 字的 MySQL 速查手册
  4. 输出全靠画html5在线玩4399,输出全靠画
  5. 安装Win10 Ubuntu20.04双系统
  6. java email bean_JavaWeb学习笔记-第四章JavaBean技术
  7. iOS多线程理解告别生硬
  8. 系统进程启动流程分析(一)
  9. 全球及中国铁矿石行业供求状况与投资决策建议报告2022版
  10. 拟一维喷管流动的数值解——亚声速-超声速等熵喷管流动的守恒型CFD解法(MacCormack方法)