( •̀ ω •́ )✧SpringSecurity中级篇
目录
- ( •̀ ω •́ )✧SpringSecurity中级篇
- 一、基于表达式的访问控制⭐
- 1、自定义方法
- 2、修改配置文件SpringSecurityConfig.java
- 3、配置用户权限
- 二、 基于注解的访问控制⭐
- 1、@Secured和@PreAuthorize小案例
- 三、免登录RememberMe功能实现
- 1、引入依赖
- 2、配置数据源
- 3、免登录具体配置RemmberConfig .java
- 5、配置和静态页面添加
- 四、Thymeleaf中SpringSecurity的集成
- 1、引入依赖
- 2、小Demo实现
- 五、退出登录
- 六、SpringSecurity中的CSRF
- 1、 什么是CSRF?
- 2、小Demo案例
- 七、Oauth2认证⭐
- 1、常用语术
- 2、特点
- 3、授权模式
- ①、授权码模式⭐
- ②、 密码模式
一、基于表达式的访问控制⭐
可以通过 access()
实现和之前学习的权限控制完成相同的功能。.antMatchers.access(“表达式或方法”) 表达式范围很广如:.anyRequest.access(@类名.方法):通过url来判断访问哪里,与获取的权限集合进行判断是否有权限(适合项目的开发)。
自定义方法Demo:
1、自定义方法
①编写Serviece层SecurityAccess.java
@Service
public class SecurityAccess {
//自定义方法
public boolean authority(HttpServletRequest request, Authentication authentication){
//获取权限列表
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
if(!CollectionUtils.isEmpty(authorities)){
//获取uri
String uri=request.getRequestURI();
//通过uri获取权限码
SimpleGrantedAuthority simple=new SimpleGrantedAuthority(uri);
if(authorities.contains(simple)){
return true;
}
}
return false;
}
}
2、修改配置文件SpringSecurityConfig.java
@SpringBootConfiguration
/*表达式、方法权限控制*/
public class SpringSecurityConfig4 extends WebSecurityConfigurerAdapter {
//引入handle
@Autowired
SecuritySuccessHandle securitySuccessHandle;
@Autowired
SecurityFailHandle securityFailHandle;
@Autowired
SecurityAccessDeniedHandler securityAccessDeniedHandler;
@Autowired
SecurityAccess securityAccess;
//配置passwordEncode的实现类
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
//重写方法配置
@Override
protected void configure(HttpSecurity http) throws Exception {
//关闭csrf
http.csrf().disable();
//配置登录页面
http.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/login")
//登陆成功handle
.successHandler(securitySuccessHandle)//Post请求
//登录失败handle
.failureHandler(securityFailHandle)
//配置登录参数
.usernameParameter("userName")
.passwordParameter("passWord");
//权限配置
http.authorizeRequests()
//一下允许访问
.antMatchers("/login.html","/login").permitAll()
//资源不被任何人访问
.antMatchers("/denyall.html").denyAll()
//access()通过Uri判断
.anyRequest().access("@securityAccess.authority(request,authentication)");
// 对403情况的处理
http.exceptionHandling()
.accessDeniedHandler(securityAccessDeniedHandler);
}
}
3、配置用户权限
@Service
/*自定义用户*/
public class SpringSecurityService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//判断用户名
if(StringUtils.isEmpty(s)||!("admin".equals(s))){
throw new UsernameNotFoundException("不存在此用户!");
}
//密码加密
String pw=passwordEncoder.encode("123456");
//返回一个用户
return new User(s,pw, AuthorityUtils.commaSeparatedStringToAuthorityList("/tomain"));
}
}
//=========================================================
//Controller设置了一个访问/tomain的处理
@GetMapping("/tomain")
public String main(){
return "欢迎登录";
}
通过上面的配置,可得出,能访问的只有/tomain,下面进行测试
二、 基于注解的访问控制⭐
- 首先需要在启动上加上注解:
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
,其中@Secured
: 是专门用于判断是否具有角色的。能写在方法或类上。参数要以ROLE_开头
。其次,@PreAuthorize
和@PostAuthorize
都是方法或类级别注解,这里对@PreAuthorize
进行解析
1、@Secured和@PreAuthorize小案例
SpringSecurityConfig5 .java文件:
@SpringBootConfiguration
/*注解权限控制*/
public class SpringSecurityConfig5 extends WebSecurityConfigurerAdapter {
//引入handle
@Autowired
SecuritySuccessHandle securitySuccessHandle;
@Autowired
SecurityFailHandle securityFailHandle;
@Autowired
SecurityAccessDeniedHandler securityAccessDeniedHandler;
//配置passwordEncode的实现类
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
//重写方法配置
@Override
protected void configure(HttpSecurity http) throws Exception {
//关闭csrf
http.csrf().disable();
//配置登录页面
http.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/login")
//登陆成功handle
.successHandler(securitySuccessHandle)//Post请求
//登录失败handle
.failureHandler(securityFailHandle)
//配置登录参数
.usernameParameter("userName")
.passwordParameter("passWord");
//权限配置
http.authorizeRequests()
//一下允许访问
.antMatchers("/login.html","/login").permitAll()
//资源不被任何人访问
.antMatchers("/denyall.html").denyAll()
//配置其他资源必须登录才能访问
.anyRequest().authenticated();//放在最后
//对403情况的处理
http.exceptionHandling()
.accessDeniedHandler(securityAccessDeniedHandler);
}
}
//没什么变化
MainController .java:
@RestController
public class MainController {
@GetMapping("/tomain")
public String main(){
return "欢迎登录";
}
@PostMapping("/toagain")
public String toagain(){
return "登录失败,请重新<a href=\"/login.html\">登录</a>";
}
/*注解角色访问*/
@Secured({"ROLE_PP"})
@GetMapping("zj")
public String tozj(){
return "注解权限访问!";
}
/*注解权限码访问*/
@PreAuthorize("hasAuthority('1000')")
@GetMapping("ma")
public String toma(){
return "注解权限码访问!";
}
最后在启动类上加上@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)即可
上面直接是对注解角色控制和权限码控制进行了,设置,/zj
和/ma
只有角色是ROLE_PP
和权限码是1000
的用户才能访问,设置用户权限如下:.
下一关🏓
三、免登录RememberMe功能实现
- 引入依赖mysql,mybatis(jdbc)
- 配置数据源application.yml
- 配置持久层和指定用户免登录
1、引入依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/>
<!-- lookup parent from repository -->
</parent>
<dependencies>
<!--spring security 组件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--web 组件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- test 组件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<!-- mybatis 依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<!-- mysql 数据库依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
</dependencies>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
2、配置数据源
application.yml:
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/security? useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: root
3、免登录具体配置RemmberConfig .java
切记重新执行时,把创表语句注释掉,避免第二次创建表
/*配置持久层对象*/
@Configuration
public class RemmberConfig {
@Autowired
private DataSource dataSource;
@Bean
public PersistentTokenRepository getPersistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
//自动建表,第一次启动时需要,第二次启动时注释掉
jdbcTokenRepository.setCreateTableOnStartup(true);
//返回一个持久层对象
return jdbcTokenRepository;
}
}
5、配置和静态页面添加
SpringSecurityConfig.java
// 记住我
http.rememberMe()
//失效时间,单位秒
.tokenValiditySeconds(60*60*24*7)
//登录逻辑交给哪个对象
.userDetailsService(springSecurityService)
// 持久层对象
.tokenRepository(remmberConfig.getPersistentTokenRepository());
login.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/login" method="post">
用户名:<input type="text" name="userName" /><br/>
密码:<input type="password" name="passWord" /><br/>
<input type="checkbox" name="remember-me" value="true"/><br/>
<input type="submit" value="登录" />
</form>
</body>
</html>
这样便代表成功了,下次就不用登录喽
四、Thymeleaf中SpringSecurity的集成
1、引入依赖
<!--thymeleaf springsecurity5 依赖-->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<!--thymeleaf依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
引入命名空间:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org" >
2、小Demo实现
在项目 resources 中新建 templates 文件夹,在 templates 中新建demo.html 页面.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras- springsecurity5">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
登录账号:<span sec:authentication="name"></span><br/>
登录用户名:<span sec:authentication="principal.username"></span><br/>
凭证:<span sec:authentication="credentials"></span><br/>
权限和角色:<span sec:authentication="authorities"></span><br/>
客户端地址:<span sec:authentication="details.remoteAddress"></span> <br/>
sessionId:<span sec:authentication="details.sessionId"></span><br/>
通过权限判断:
<button sec:authorize="hasAuthority('/insert')">新增</button>
<button sec:authorize="hasAuthority('/delete')">删除</button>
<button sec:authorize="hasAuthority('/update')">修改</button>
<button sec:authorize="hasAuthority('/select')">查看</button> <br/>
通过角色判断:
<button sec:authorize="hasRole('a')">新增</button>
<button sec:authorize="hasRole('b')">删除</button>
<button sec:authorize="hasRole('c')">修改</button>
<button sec:authorize="hasRole('d')">查看</button>
<a href="/logout">退出登录</a>
</body>
</html>
编写Controller层进行页面的跳转
@Controller
public class TestController {
/*thyemleaf*/
@RequestMapping("demo")
public String todemo(){
return "demo";
}
}
设置角色权限,通过角色的权限不同,thymeleaf显示不同的内容!
//返回一个用户
return new User(s,pw, AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_b,ROLE_a,/insert ,/delete"));
结果:
小结:以上看出,通过角色和允许访问的uri不同,只显示了增加和删除以及用户信息,修改这些都没有显示,通过视图的设置实现了不同权限,看见不同的内容
五、退出登录
很简单只需要向项目发出/loginOut请求即可只要在页面中添加 /logout 的超链接即可,下面进行实现(添加超链接)。
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras- springsecurity5">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
登录账号:<span sec:authentication="name"></span><br/>
登录用户名:<span sec:authentication="principal.username"></span><br/>
凭证:<span sec:authentication="credentials"></span><br/>
权限和角色:<span sec:authentication="authorities"></span><br/>
客户端地址:<span sec:authentication="details.remoteAddress"></span> <br/>
sessionId:<span sec:authentication="details.sessionId"></span><br/>
通过权限判断:
<button sec:authorize="hasAuthority('/insert')">新增</button>
<button sec:authorize="hasAuthority('/delete')">删除</button>
<button sec:authorize="hasAuthority('/update')">修改</button>
<button sec:authorize="hasAuthority('/select')">查看</button> <br/>
通过角色判断:
<button sec:authorize="hasRole('a')">新增</button>
<button sec:authorize="hasRole('b')">删除</button>
<button sec:authorize="hasRole('c')">修改</button>
<button sec:authorize="hasRole('d')">查看</button>
<a href="/logout">退出登录</a>
</body>
</html>
添加配置配置SpringSecurityConfig.java
http.logout()
//退出的访问路径
.logoutUrl("/logout")
.logoutSuccessUrl("/login.html");
六、SpringSecurity中的CSRF
1、 什么是CSRF?
- CSRF(Cross-site request forgery)跨站请求伪造,也被称为“OneClick Attack” 或者
- Session Riding。通过伪造用户请求访问受信任站点的非法请求访问。
跨域:只要网络协议,ip 地址,端口中任何一个不相同就是跨域请求。 - 客户端与服务进行交互时,由于 http 协议本身是无状态协议,所以引入了cookie进行记录客户端身份。在cookie中会存放session id用来识别客户端身份的。在跨域的情况下,session id可能被第三方恶意劫持,通过这个 session id 向服务端发起请求时,服务端会认为这个请求是合法的,可能发生很多意想不到的事情。
2、小Demo案例
前端页面使用thymeleaf模板,新建templates/login.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org" >
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/login" method="post">
<input type="hidden" th:value="${_csrf.token}" name="_csrf" th:if="${_csrf}"/>
用户名:<input type="text" name="userName" /><br/>
密码:<input type="password" name="passWord" /><br/>
<input type="submit" value="登录" />
</form>
</body>
</html>
编写Controller层访问showLogin时跳转:
/*showLogin跳转*/
@RequestMapping("showLogin")
public String tologin(){
return "login";
}
记得注释掉ServiecSecurityConfig中:// http.csrf().disable();测试结果:
这样就获取到token.通行令牌
七、Oauth2认证⭐
介绍:第三方认证技术方案最主要是解决认证协议的通用标准问题,因为要实现跨系统认证,各系统之间要遵循一定的接口协议。OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准。大大节约了程序员的时间,提供便捷和效率。
1、常用语术
2、特点
- 优点:
更安全,客户端不接触用户密码,服务器端更易集中保护
广泛传播并被持续采用
短寿命和封装的token
资源服务器和授权服务器解耦
集中式授权,简化客户端
HTTP/JSON友好,易于请求和传递token
考虑多种客户端架构场景
客户可以具有不同的信任级别 - 缺点:
协议框架太宽泛,造成各种实现的兼容性和互操作性差
不是一个认证协议,本身并不能告诉你任何用户信息。
3、授权模式
- 授权码模式
- 密码模式
①、授权码模式⭐
用户登录,通过配置用户,登录访问指定的授权服务器,授权服务器会返回给用户一个授权码,此时用户拿着授权码,访问oauth/token,去兑换令牌,获得令牌之后,再次访问资源服务器,此时拿去资源。
(1)依赖的引入
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/>
<!-- lookup parent from repository -->
</parent>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
(2) 编写实体类User.java
public class User implements UserDetails {
private String username;
private String password;
private List<GrantedAuthority> authorities;
public User(String username, String password, List<GrantedAuthority> authorities) {
this.username = username;
this.password = password;
this.authorities = authorities;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
(3)编写UserService.java配置用户账号密码
@Service
public class UserService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String password = passwordEncoder.encode("123456");
return new User("admin",password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}
(4) 编写UserController.java
@Controller
@RequestMapping("user")
public class UserController {
@RequestMapping("/getCurrentUser")
@ResponseBody
public Object getCurrentUser(Authentication authentication) {
return authentication.getPrincipal();
}
}
(5)编写配置类Oauth2Config .javaSpring Security 配置类
@SpringBootConfiguration
@EnableWebSecurity
public class Oauth2Config extends WebSecurityConfigurerAdapter {
//配置passwordEncode的实现类对象
@Bean
public PasswordEncoder getPE(){
return new BCryptPasswordEncoder();
}
//配置权限
@Override
protected void configure(HttpSecurity http) throws Exception {
//关闭csrf
http.csrf() .disable()
//放行请求
.authorizeRequests()
.antMatchers("/oauth/**", "/login/**", "/logout/**")
.permitAll()
//除以上请求都拦截
.anyRequest() .authenticated()
.and()
//放行登录界面
.formLogin() .permitAll();
}
}
授权服务器配置AuthorizationServerConfig.java
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
//配置client_id
.withClient("admin")
//配置client-secret
.secret(passwordEncoder.encode("112233"))
//配置redirect_uri,用于授权成功后跳转
.redirectUris("http://www.baidu.com")
//配置申请的权限范围
.scopes("all")
//配置grant_type,表示授权类型
.authorizedGrantTypes("authorization_code","password");
}
}
资源服务器配置ResourceServerConfig.java
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.requestMatchers()
.antMatchers("/user/**");//配置需要保护的资源路径
}
}
测试结果
获取授权码:
http://localhost:8080/oauth/authorize?response_type=code&client_id=admin&redirect_uri=http://www.baidu.com&scope=all
获取到授权码后,使用PostMan软件进行http://localhost:8080/oauth/token访问,并配置:》》》
发送请求后获取到令牌-----》》》》》》:
配置上令牌进行访问:http://localhost:8080/user/getCurrentUser,通过令牌获取资源
如果修改token令牌则会报错
②、 密码模式
密码模式与授权模式相似,但相对而言密码模式相对简单,它只需要通过密码账号直接发送过去,就可以获得一个令牌通行证,即可获取到用户信息,使用的场景比如;微信和QQ ,两者都是一家产业,所以用户数据互通,完全不需要授权码去验证,免去了中间的繁琐,但也注定密码模式的格局小了,所以这里大致了解一下(重点还是授权码模式
)。小案例演示:
(1) 只需要在授权码模式
的基础上稍微修改即可
在Spring Security Config.java配置文件增加内容
@Bean
@Override//身份验证管理器
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
//授权服务器配置类中需要用到AuthenticationManager接口,所以在这里配置他的实现类
(2) AuthorizationServerConfig.java增加内容:
@Autowired
private AuthenticationManager authenticationManager;//认证管理器
/**
* 密码模式
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userService);
}
测试:
这样就测出来啦,可以将其存放在Redis中方便使用 完~