同步操作将从 小牛肉/cs-wiki 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
在上节我们使用基于内存的方式体验了下 Spring Security,在实际项目中,都是需要数据库进行操作的,本节使用 hsqldb 内存数据库进行说明。
这里我们使用 Spring Data JPA 操作数据库,需要一个用户的实体类,再者需要定义相应的 Service 获取用户的信息,最后重写 UserDetailsService
的 loadUserByUsername
方法从数据库中获取用户信息,传给 Spring Security 进行处理。
添加 SpringData JPA 和 hsqldb 的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<scope>runtime</scope>
</dependency>
@Configuration
@EnableWebSecurity // 开启Spring Security的功能
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
和上节一样,只不过我们不需要实现认证和授权方法。
创建 UserInfo
实体类:
@Entity
public class UserInfo {
public enum Role{
admin, normal
}
@Id // 主键
@GeneratedValue // 自增
private long uid;
private String username;
private String password;
@Enumerated(EnumType.STRING)
private Role role;
// Getter And Setter
}
🔺 这里我们将
Role
写死了,后面会将Role
作为一张数据库表进行动态加载
创建和数据库交互的 UserInfoRepository
:
public interface UserInfoRepository extends JpaRepository<UserInfo,Long> {
public UserInfo findByUsername(String username);
}
创建 UserInfoService
:
public interface UserInfoService {
public UserInfo findByUsername(String username);
}
创建 UserInfoService
的实现类:
@Service
public class UserInfoServiceImpl implements UserInfoService {
@Autowired
private UserInfoRepository userInfoRepository;
@Override
public UserInfo findByUsername(String username) {
return userInfoRepository.findByUsername(username);
}
}
🚩 我们需要继承一个 UserDetailSevice
接口并且加入到容器中,实现 loadUserByUsername
方法,里面的逻辑通常是从数据库查找出对应用户名的密码然后构造一个 UserDetail
对象,Spring Security会根据返回的这个带有正确用户信息的对象和前台传过来的用户名密码进行比对来判断是否认证通过。
实现 UserDetailsService
类的 loadUserByUsername
方法:
import org.springframework.security.core.userdetails.UserDetailsService;
......
@Service
public class CustomUserDetailService implements UserDetailsService {
@Autowired
private UserInfoService userInfoService;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("CustomUserDetailService.loadUserByUsername()");
// 通过 username 获取用户信息
UserInfo userInfo = userInfoService.findByUsername(username);
if(userInfo == null)
throw new UsernameNotFoundException("not found");
// 定义权限列表
List<GrantedAuthority> authorities = new ArrayList<>();
// 用户所拥有的权限 必须以 ROLE_ 开头
authorities.add(new SimpleGrantedAuthority("ROLE_" + userInfo.getRole().name()));
// 这里的密码需要使用PasswordEncoder进行加密,否则会报错
User userDetails = new User(userInfo.getUsername(), passwordEncoder.encode(userInfo.getPassword()), authorities);
return userDetails;
}
}
和上节一样:
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello() {
return "Hello,Spring Security";
}
@RequestMapping("/helloAdmin")
@PreAuthorize("hasAnyRole('admin')") // 只允许角色 admin 访问
public String helloAdmin() {
return "Hello,admin";
}
@RequestMapping("/helloUser")
@PreAuthorize("hasAnyRole('admin','normal')") // 允许角色 admin 和 normal 访问
public String helloUser() {
return "Hello,user";
}
}
这里我们使用一个 DataInit
类,初始化两个账号admin/123和user/123:
@Service
public class DataInit {
@Autowired
UserInfoRepository userInfoRepository;
@PostConstruct
public void dataInit(){
// 用户 admin
UserInfo admin = new UserInfo();
admin.setUsername("admin");
admin.setPassword("123");
admin.setRole(UserInfo.Role.admin);
userInfoRepository.save(admin);
// 用户 user
UserInfo user = new UserInfo();
user.setUsername("user");
user.setPassword("123");
user.setRole(UserInfo.Role.normal);
userInfoRepository.save(user);
}
}
测试账号:admin/123,上面的两个地址应该都是可以正常访问的。
测试账号:user/123,只能访问 http://127.0.0.1:8080/helloUser
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。