最简实例:springboot+springsecurity+JPA+mysql实现登陆限制

写在前面

其实核心是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)
所以我们需要做的是:

  1. 实现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();
}
  1. 配置权限规则,规定哪些角色可以访问哪些内容
  2. 必要步骤,密码加密。

实现过程

  1. 建立model,repository
  2. 建立service,service根据用户名拿到用户的角色(role)
  3. 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";
    }
}

写在后面

记录学习,欢迎交流,多多指教

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章