寫在前面
其實核心是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";
}
}
寫在後面
記錄學習,歡迎交流,多多指教