基本上,在所有的开发的系统中,都必须做认证(authentication)和授权(authorization),以保证系统的安全性。考虑到很多胖友对认证和授权有点分不清楚,在这里引用一个网上有趣的例子
以论坛举例子:
所以简单来说:认证解决“你是谁”的问题,授权解决“你能做什么”的问题。
在 Java 生态中,目前有 Spring Security 和 Apache Shiro 两个安全框架,可以完成认证和授权的功能。本文,我们先来学习下 Spring Security 。
示例代码对应仓库:https://gitee.com/todostyle/tupay-learning.git
learn-springsecurity-demo
在本小节中,我们来快速入门下 Spring Security ,实现访问 API 接口时,需要首先进行登录,才能进行访问。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>tupay-learning</artifactId>
<groupId>com.tostyle</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>learn-springsecurity-demo</artifactId>
<dependencies>
<!-- 实现对 Spring MVC 的自动化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 实现对 Spring Security 的自动化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
</project>
package com.tostyle.springsecurity;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author tostyle
* @date 2023-02-27 10:03
*/
@SpringBootApplication
public class SpringSecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SpringSecurityApplication.class, args);
}
}
spring:
# Spring Security 配置项,对应 SecurityProperties 配置类
security:
# 配置默认的 InMemoryUserDetailsManager 的用户账号与密码。
user:
name: user # 账号
password: user # 密码
roles: ADMIN # 拥有角色
这里,我们添加了 spring.security.user 配置项,UserDetailsServiceAutoConfiguration 会基于配置的信息创建一个用户 User 在内存中。
如果,我们未添加 spring.security.user 配置项,UserDetailsServiceAutoConfiguration 会自动创建一个用户名为 "user" ,密码为 UUID 随机的用户 User 在内存中。
在 com.tostyle.springsecurity.controller 包路径下,创建 HelloController 类,代码如下:
package com.tostyle.springsecurity.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author tostyle
* @date 2023-02-27 10:33
*/
@RestController
@RequestMapping("/hello")
public class HelloController {
@GetMapping("/demo")
public String demo() {
return "Hello SpringSecurity";
}
}
这里,我们先提供一个 "/hello/demo" 接口,用于测试未登录时,会被拦截到登录界面。
执行 SpringSecurityApplication#main(String[] args) 方法,运行项目。
项目启动成功后,浏览器访问 http://127.0.0.1:8080/hello/demo 接口。因为未登录,所以被 Spring Security 拦截到登录界面。如下图所示:
因为我们没有自定义登录界面,所以默认会使用 DefaultLoginPageGeneratingFilter 类,生成上述界面。
输入我们在「2.3 配置文件」中配置的「user/user」账号,进行登录。登录完成后,因为 Spring Security 会记录被拦截的访问地址,所以浏览器自动动跳转 http://127.0.0.1:8080/admin/demo 接口。访问结果如下图所示:
代码示例
learn-springsecurity-demo-role
在「2. 快速入门」中,我们很快速的完成了 Spring Security 的入门。本小节,我们将会自定义 Spring Security 的配置,实现权限控制。
考虑到不污染上述的示例,我们新建一个 learn-springsecurity-demo-role 项目。
和 「2.1 引入依赖」 一致,见 pom.xml 文件。
在示例一中,我们会看看如何自定义 Spring Security 的配置,实现权限控制。
在 com.tostyle.springsecurity.config 包下,创建 SecurityConfig 配置类,继承 WebSecurityConfigurerAdapter 抽象类,实现 Spring Security 在 Web 场景下的自定义配置。代码如下:
package com.tostyle.springsecurity.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* @author tostyle
* @date 2023-02-27 10:58
*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
}
}
首先,我们重写 #configure(AuthenticationManagerBuilder auth) 方法,实现 AuthenticationManager 认证管理器。代码如下:
// SecurityConfig.java
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.
// <X> 使用内存中的 InMemoryUserDetailsManager
inMemoryAuthentication()
// <Y> 不使用 PasswordEncoder 密码编码器
.passwordEncoder(NoOpPasswordEncoder.getInstance())
// <Z> 配置 admin 用户
.withUser("admin").password("admin").roles("ADMIN")
// <Z> 配置 normal 用户
.and().withUser("normal").password("normal").roles("NORMAL");
}
* Spring 内置了两种 UserDetailsManager 实现:
* InMemoryUserDetailsManager,和「2. 快速入门」是一样的。
* JdbcUserDetailsManager ,基于 JDBC的 JdbcUserDetailsManager 。
* 实际项目中,我们更多采用调用 AuthenticationManagerBuilder#userDetailsService(userDetailsService) 方法,使用自定义实现的 UserDetailsService 实现类,更加灵活且自由的实现认证的用户信息的读取。
* 在这里,为了方便,我们使用 NoOpPasswordEncoder 。实际上,等于不使用 PasswordEncoder ,不配置的话会报错。
* 生产环境下,推荐使用 BCryptPasswordEncoder 。更多关于 PasswordEncoder 的内容,推荐阅读《该如何设计你的 PasswordEncoder?》文章。
然后,我们重写 #configure(HttpSecurity http) 方法,主要配置 URL 的权限控制。代码如下:
// SecurityConfig.java
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// <X> 配置请求地址的权限
.authorizeRequests()
.antMatchers("/test/echo").permitAll() // 所有用户可访问
.antMatchers("/test/admin").hasRole("ADMIN") // 需要 ADMIN 角色
.antMatchers("/test/normal").access("hasRole('ROLE_NORMAL')") // 需要 NORMAL 角色。
// 任何请求,访问的用户都需要经过认证
.anyRequest().authenticated()
.and()
// <Y> 设置 Form 表单登录
.formLogin()
// .loginPage("/login") // 登录 URL 地址
.permitAll() // 所有用户可访问
.and()
// 配置退出相关
.logout()
// .logoutUrl("/logout") // 退出 URL 地址
.permitAll(); // 所有用户可访问
}
* #(String... antPatterns) 方法,配置匹配的 URL 地址,基于 Ant 风格路径表达式 ,可传入多个。
* 【常用】#permitAll() 方法,所有用户可访问。
* 【常用】#denyAll() 方法,所有用户不可访问。
* 【常用】#authenticated() 方法,登录用户可访问。
* #anonymous() 方法,无需登录,即匿名用户可访问。
* #rememberMe() 方法,通过 remember me 登录的用户可访问。
* #fullyAuthenticated() 方法,非 remember me 登录的用户可访问。
* #hasIpAddress(String ipaddressExpression) 方法,来自指定 IP 表达式的用户可访问。
* 【常用】#hasRole(String role) 方法, 拥有指定角色的用户可访问。
* 【常用】#hasAnyRole(String... roles) 方法,拥有指定任一角色的用户可访问。
* 【常用】#hasAuthority(String authority) 方法,拥有指定权限(authority)的用户可访问。
* 【常用】#hasAuthority(String... authorities) 方法,拥有指定任一权限(authority)的用户可访问。
* 【最牛】#access(String attribute) 方法,当 Spring EL 表达式的执行结果为 true 时,可以访问。
* 如果想要自定义登录页面,可以通过 #loginPage(String loginPage) 方法,来进行设置。不过这里我们希望像「2. 快速入门」一样,使用默认的登录界面,所以不进行设置。
* 如果想要自定义退出页面,可以通过 #logoutUrl(String logoutUrl) 方法,来进行设置。不过这里我们希望像「2. 快速入门」一样,使用默认的退出界面,所以不进行设置。
在com.tostyle.springsecurity.controller包路径下,创建 TestController 类,提供测试 API 接口。代码如下:
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("/echo")
public String demo() {
return "示例返回";
}
@GetMapping("/home")
public String home() {
return "我是首页";
}
@GetMapping("/admin")
public String admin() {
return "我是管理员";
}
@GetMapping("/normal")
public String normal() {
return "我是普通用户";
}
}
在示例二中,我们会看看如何使用 Spring Security 的注解,实现权限控制。
修改 SecurityConfig 配置类,增加 @EnableGlobalMethodSecurity 注解,开启对 Spring Security 注解的方法,进行权限验证。代码如下:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
在com.tostyle.springsecurity.controller包路径下,创建 HelloController 类,提供测试 API 接口。代码如下:
@RestController
@RequestMapping("/hello")
public class DemoController {
@PermitAll
@GetMapping("/echo")
public String demo() {
return "示例返回";
}
@GetMapping("/home")
public String home() {
return "我是首页";
}
@PreAuthorize("hasRole('ROLE_ADMIN')")
@GetMapping("/admin")
public String admin() {
return "我是管理员";
}
@PreAuthorize("hasRole('ROLE_NORMAL')")
@GetMapping("/normal")
public String normal() {
return "我是普通用户";
}
}
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。