下面是“SpringBoot+SpringSecurity实现基于真实数据的授权认证”的完整攻略:
1. 简介
Spring Security 是 Spring 社区中安全领域的一部分,它提供了强大且可高度定制化的身份验证和授权框架。同时,Spring Security 还可以与 Spring 的其他模块轻松集成,比如:Spring Boot、Spring MVC、Spring Data等。
本文主要针对如何使用 Spring Boot 和 Spring Security 实现基于真实数据的授权认证,并分别给出了两个示例。
2. 实现步骤
下面是具体的实现步骤:
2.1 引入 Spring Security 依赖
在 pom.xml 文件中添加如下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2.2 配置 Spring Security
在 Spring Boot 的配置文件 application.properties 或 application.yml 中添加如下配置:
# 禁用 Spring Security 自带的 CSRF 防护
security.enable-csrf=false
# 配置用户信息存储格式
spring.security.user.name=admin
spring.security.user.password=admin123
spring.security.user.roles=admin, user
该配置文件设置了禁用 CSRF 防护功能,指定用户名和密码以及用户角色。
2.3 编写授权接口
在 Spring Boot 项目中,通过注解来标记相应的请求,以此来实现对指定接口的授权访问控制。
例如,下面的代码中通过 @Secured
注解来标记了一个需要 admin 角色才能访问的 API 接口。
@RestController
@RequestMapping("/api")
public class DemoController {
@Secured({"ROLE_ADMIN"})
@GetMapping("/hello")
public String hello() {
return "Hello World!";
}
}
2.4 测试授权
在浏览器中输入 http://localhost:8080/api/hello
,此时会跳转到 Spring Security 的默认登录页面。输入用户名和密码之后,如果用户拥有 admin 角色,则可以成功访问 hello
接口。
3. 示例一
本示例中,我们将通过 JPA 存储用户信息和权限信息,以此来实现基于真实数据的授权认证。
3.1 引入 JPA 依赖
在 pom.xml 文件中添加如下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
3.2 创建用户信息和权限信息实体类
创建用户信息实体类 User 和权限信息实体类 Permission。
@Entity
@Table(name = "user")
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
@ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinTable(name = "user_permission",
joinColumns = {@JoinColumn(name = "user_id")},
inverseJoinColumns = {@JoinColumn(name = "permission_id")})
private Set<Permission> permissions;
// 省略 getter 和 setter
}
@Entity
@Table(name = "permission")
public class Permission implements GrantedAuthority {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// 省略 getter 和 setter
}
注:User 类实现了 UserDetails 接口,而 Permission 类实现了 GrantedAuthority 接口,这样可以同时存储权限和用户信息,方便后续认证。
3.3 创建 UserRepository 和 PermissionRepository 接口
创建 UserRepository 和 PermissionRepository 两个接口来继承 JpaRepository 接口,以此来实现对用户和权限信息的数据 CRUD 操作。
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}
public interface PermissionRepository extends JpaRepository<Permission, Long> {
Permission findByName(String name);
}
在 UserRepository 接口中定义了根据用户名来查找用户的方法,PermissionRepository 接口中定义了根据权限名来查找权限的方法。
3.4 实现 UserDetailsService 接口
在 Spring Security 中,UserDetailsService 接口用来从数据库中根据用户名查询用户信息,并返回一个 UserDetails 对象,该对象包含了用户名、密码和用户所拥有的权限等信息。我们需要创建一个实现 UserDetailsService 接口的类 UserDetailsServiceImpl。
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}
return new User(user.getUsername(), user.getPassword(), user.getPermissions());
}
}
在代码中,我们通过 UserRepository 接口来查询数据库中的用户信息,然后将查询结果封装成一个 UserDetails 对象并返回。
3.5 配置 Spring Security
在 Spring Boot 的配置文件 application.yml 中增加如下配置:
spring.jpa.hibernate.ddl-auto=create
spring.datasource.url=jdbc:mysql://localhost:3306/demo?useSSL=false&serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.show-sql=true
security.enable-csrf=false
spring.security.user.name=admin
spring.security.user.password=admin123
spring.security.user.roles=admin
spring.profiles.active=jpa
该配置文件中定义了数据库相关信息和 JPA 相关配置,配置文件中还指定了用户名、密码和用户角色信息。
3.6 编写授权接口
在 Spring Boot 项目中,可以通过注解来标记相应的请求,以此来实现对指定接口的授权访问控制。例如,下面的代码中通过 @PreAuthorize
注解来标记只有具有 admin 角色和 USER_VIEW 权限的用户才能访问接口。
@RestController
@RequestMapping("/api")
public class UserController {
@Autowired
private UserRepository userRepository;
@Autowired
private PermissionRepository permissionRepository;
@PreAuthorize("hasRole('ADMIN') and hasPermission(null,'USER_VIEW')")
@GetMapping("/users")
public List<User> getUsers() {
return userRepository.findAll();
}
@PreAuthorize("hasRole('ADMIN') and hasPermission(null,'USER_DELETE')")
@DeleteMapping("/users/{id}")
public void deleteUser(@PathVariable Long id) {
userRepository.deleteById(id);
}
}
3.7 测试授权
在浏览器中输入 http://localhost:8080/api/users
,此时会跳转到 Spring Security 的默认登录页面。输入用户名和密码之后,如果用户拥有 admin 角色并且具有 USER_VIEW 权限,则可以查看所有用户信息。另外,删除用户需要具备 USER_DELETE 权限。
4. 示例二
本示例中,我们将通过 JWT 存储用户信息和权限信息,以此来实现基于真实数据的授权认证。
4.1 引入依赖
在 pom.xml 文件中添加如下依赖:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.6.0</version>
</dependency>
4.2 创建 JWT 相关类
创建 JwtUtils 和 JwtAuthenticationToken 类。
JwtUtils 类用于生成和解析 JWT,如下所示。
@Component
public class JwtUtils {
@Value("${jwt.secret}")
private String secret;
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put("sub", userDetails.getUsername());
claims.put("created", new Date());
claims.put("roles", userDetails.getAuthorities());
return Jwts.builder().setClaims(claims).setExpiration(new Date(System.currentTimeMillis() + 60 * 60 * 1000))
.signWith(SignatureAlgorithm.HS512, secret).compact();
}
public String getUsernameFromToken(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody().getSubject();
}
}
JwtAuthenticationToken 类用于包装认证的 token。
public class JwtAuthenticationToken extends UsernamePasswordAuthenticationToken {
private String token;
public JwtAuthenticationToken(String token) {
super(null, null);
this.token = token;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
}
4.3 实现 AuthenticationProvider 接口
我们需要创建一个实现 AuthenticationProvider 接口的类 JwtAuthenticationProvider。AuthenticationProvider 接口是 Spring Security 提供的一个核心接口,用于处理认证逻辑。
@Component
public class JwtAuthenticationProvider implements AuthenticationProvider {
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
private JwtUtils jwtUtils;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
JwtAuthenticationToken jwtAuthenticationToken = (JwtAuthenticationToken) authentication;
String token = jwtAuthenticationToken.getToken();
String username = jwtUtils.getUsernameFromToken(token);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtUtils.validateToken(token, userDetails)) {
return new JwtAuthenticationToken(token, userDetails.getAuthorities());
}
throw new BadCredentialsException("Token 校验失败");
}
@Override
public boolean supports(Class<?> authentication) {
return JwtAuthenticationToken.class.isAssignableFrom(authentication);
}
}
在代码中,我们通过调用 JwtUtils 类的 validateToken 方法来校验 token 的有效性。
4.4 配置 Spring Security
在 Spring Boot 的配置文件 application.yml 中增加如下配置:
security.enabled=false
jwt.secret=secret
jwt.header=Authorization
jwt.prefix=Bearer
jwt.expiration=3600
jwt.path=/api/authenticate
该配置文件中定义了 JWT 相关的配置信息。
4.5 编写认证接口
在 Spring Boot 项目中,可以通过注解来标记相应的请求,以此来实现对指定接口的授权访问控制。例如,下面的代码中通过使用 Spring Security 的 formLogin() 方法来指定认证 API 的路径。
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationProvider jwtAuthenticationProvider;
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
http.csrf().disable().authorizeRequests().antMatchers("/api/authenticate").permitAll().anyRequest()
.authenticated().and().formLogin().permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(jwtAuthenticationProvider);
}
}
@RestController
@RequestMapping("/api")
public class AuthController {
private static final String AUTHENTICATION_SUCCESSFUL = "认证成功";
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtUtils jwtUtils;
@PostMapping("/authenticate")
public ResponseEntity<TokenDto> createAuthenticationToken(@RequestBody AuthenticateDto authenticationRequest) {
Authentication authentication = authenticationManager.authenticate(
new JwtAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
final String token = jwtUtils.generateToken(userDetails);
return ResponseEntity.ok(new TokenDto(token, AUTHENTICATION_SUCCESSFUL));
}
}
在代码中,我们使用 Spring Security 的 formLogin() 方法指定了认证 API 的路径,同时将 JwtAuthenticationFilter 过滤器添加到了 Spring Security 的过滤器链中。
4.6 编写授权接口
在 Spring Boot 项目中,可以通过注解来标记相应的请求,以此来实现对指定接口的授权访问控制。例如,下面的代码中通过 @PreAuthorize
注解来标记只有具有 admin 角色和 USER_VIEW 权限的用户才能访问接口。
@RestController
@RequestMapping("/api")
public class UserController {
@Autowired
private UserRepository userRepository;
@PreAuthorize("hasRole('ADMIN') and hasPermission(null,'USER_VIEW')")
@GetMapping("/users")
public List<User> getUsers() {
return userRepository.findAll();
}
@PreAuthorize("hasRole('ADMIN') and hasPermission(null,'USER_DELETE')")
@DeleteMapping("/users/{id}")
public void deleteUser(@PathVariable Long id) {
userRepository.deleteById(id);
}
}
4.7 测试授权
在浏览器中输入 http://localhost:8080/api/users
,此时会跳转到登录页面。输入用户名和密码之后,如果用户拥有 admin 角色并且具有 USER_VIEW 权限,则可以查看所有用户信息。另外,删除用户需要具备 USER_DELETE 权限。输入用户名和密码之后,会返回一个 JWT Token。在接下来的每个请求中都需要在请求头中添加该 Token 信息,这样后台才能验证该 Token 的有效性。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:SpringBoot+SpringSecurity实现基于真实数据的授权认证 - Python技术站