写在前面
其实核心是springboot+springsecurity实现登陆限制,JPA+mysql只是数据库和接口的组合,可以切换成任意其它的组合,只是数据库访问的代码就得相应地变化。
代码结构
数据表
//application.properties
spring.datasource.url=jdbc:mysql://127.0.0.1/test_spring_security?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&useSSL=true
spring.datasource.username=root
spring.datasource.password=123
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.properties.hibernate.hbm2ddl.auto=update
spring.jpa.show-sql= true
就不附sql语句了,就这几条记录,自己手动添加吧。(密码加密部分在文末)
security实现原理简述
注:以下全是我一个初学者的个人看法,记录思路而已,很可能是错的,hhhhhhhhhhhhhhhhhhh。
一句话概述: security拦截请求,需根据用户名、密码登陆,以用户名找到数据库内的用户,检索出该用户的角色(role),可自定义配置不同的角色有什么权限。(下图为security默认登陆页)
例:假设我的数据表为(id,username,password,role),登陆只验证用户名、密码,正确则根据username找到该用户,并拿出他的role值(假设值为“root”),那我们可以在springsecurity的配置中设置:某个url比如“/index”只能“root”访问。
springboot尽可能多的帮我们自动配置security,但是有一些东西必然是要根据自己的业务逻辑自行配置,比如访问数据库的service(不同的数据库、不同的访问接口也对应着不同的service写法)、权限的自定义分配(这个肯定根据你的业务逻辑分配)等问题,所以security会预留这些接口,等开发者去实现(implements)
所以我们需要做的是:
- 实现security内一个叫做UserDetailsService的接口,重写方法时,我们要根据用户名找到用户,将role信息写入实体类并return,这个实体类需要实现security内的UserDetails接口。
package org.springframework.security.core.userdetails;
public interface UserDetailsService {
UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
- 配置权限规则,规定哪些角色可以访问哪些内容
- 必要步骤,密码加密。
实现过程
- 建立model,repository
- 建立service,service根据用户名拿到用户的角色(role)
- spring security配置,根据角色的不同,分配不同的权限
1.普通的JPA流程
建立model内的User(映射保存用户名、密码的表)和Role(映射保存role的表)
User需要实现上面说的的接口UserDetails,所以要重写接口内的方法:
@Entity(name = "user")//import和getter setter就省略了
public class User implements UserDetails {
@Id //主键
@GeneratedValue(strategy = GenerationType.IDENTITY) //自增主键
private Integer id;
@Column(name ="username")
private String username;
@Column(name ="password")
private String password;
@Transient//此注解表示不与数据表映射
private List<Role> roles;
@Override//注:默认下面四个函数return的都是flase,为了简单,都改成true,不然还要做其它配置
public boolean isAccountNonExpired() { return true; }
@Override
public boolean isAccountNonLocked() { return true; }
@Override
public boolean isCredentialsNonExpired() { return true;}
@Override
public boolean isEnabled() { return true; }
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> authorities= new ArrayList<>();
for (Role role : roles) {
authorities.add(new SimpleGrantedAuthority("ROLE_"+role.getName()));
}
return authorities;
}
}
Role:
@Entity(name = "role")//import和getter setter就省略了
public class Role {
@Id //主键
@GeneratedValue(strategy = GenerationType.IDENTITY) //自增主键
private Integer id;
@Column(name ="name")
private String name;
@Column(name ="name_zh")
private String name_zh;
继承 JpaRepository
UserRepository
public interface UserRepository extends JpaRepository<User,Long> {
public User findByUsername(String username);
}
RoleRepository
public interface RoleRepository extends JpaRepository<Role,Long> {
public List<Role> findById(Integer id);
}
2.创建服务
创建service.UserService,实现接口UserDetailsService
@Service
public class UserService implements UserDetailsService {
@Resource
UserRepository userRepository;
@Resource
RoleRepository roleRepository;
@Override
public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
User user = userRepository.findByUsername(name);
if (user==null){
throw new UsernameNotFoundException("用户名不存在");
}
user.setRoles( roleRepository.findById( user.getId()));
return user;
}
}
UserService 需要实现 UserDetailsService,所以要重写loadUserByUsername方法。代码挺简单的,就不写注释了。
3.springsecurity配置
新建config.SecurityConfig,继承WebSecurityConfigurerAdapter
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserService userService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//设置userDetailsService为我们自己建的userService
//可以按住ctrl点击下面的userDetailsService到源码内看看
auth.userDetailsService(userService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/favicon.ico","/css/**","/common/**","/js/**","/images/**","/login","/userLogin","/login-error").permitAll()//静态资源,都可访问
.antMatchers("/root/**").hasRole("root")//"/root/**"只有root能访问
.antMatchers("user/**").hasRole("user")
.anyRequest().authenticated() //其它请求只需登陆即可
.and()
.formLogin();
}
//密码加密
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
关于密码加密,可以在test先对密码加密并用控制台输出,然后将输出结果保存到数据库。
@Test
void contextLoads() {
System.out.println(new BCryptPasswordEncoder().encode("234"));
}
//输出结果如下
$2a$10$dPSbxHS1U6izO.Xw7TrFKOuVcB25YYi9L2QGwaWLd1F51wK8oeQau
4.controller
写个看看效果
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(){
return "hello security";
}
@GetMapping("/root/hello")
public String root(){
return "hello root";
}
@GetMapping("/user/hello")
public String user(){
return "hello user";
}
}
写在后面
记录学习,欢迎交流,多多指教