最簡實例: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";
    }
}

寫在後面

記錄學習,歡迎交流,多多指教

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