SpringBoot淺析安全管理之基于數(shù)據(jù)庫認(rèn)證
1. 設(shè)計數(shù)據(jù)表
用戶表、角色表、用戶角色關(guān)聯(lián)表
建表語句
CREATE TABLE `role` ( `id` int(11) NOT NULL, `name` varchar(32) DEFAULT NULL, `nameZh` varchar(32) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `user` ( `id` int(11) NOT NULL, `username` varchar(32) DEFAULT NULL, `password` varchar(255) DEFAULT NULL, `enabled` varchar(1) DEFAULT NULL, `locked` varchar(1) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `user_role` ( `id` int(11) NOT NULL, `uid` int(11) DEFAULT NULL, `rid` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
初始化數(shù)據(jù)
注意:角色名有一個默認(rèn)的前綴 ROLE_
INSERT INTO `user`(`id`, `username`, `password`, `enabled`, `locked`) VALUES (1, 'root', '$2a$10$x6CBW1qnQqKPIxUSefZN7ebfTEiYNnfzzVjPJzlRhg5XyMzSWoO4e', '1', '0'); INSERT INTO `user`(`id`, `username`, `password`, `enabled`, `locked`) VALUES (2, 'admin', '$2a$10$x6CBW1qnQqKPIxUSefZN7ebfTEiYNnfzzVjPJzlRhg5XyMzSWoO4e', '1', '0'); INSERT INTO `user`(`id`, `username`, `password`, `enabled`, `locked`) VALUES (3, 'tangsan', '$2a$10$x6CBW1qnQqKPIxUSefZN7ebfTEiYNnfzzVjPJzlRhg5XyMzSWoO4e', '1', '0'); INSERT INTO `role`(`id`, `name`, `nameZh`) VALUES (1, 'ROLE_dba', '數(shù)據(jù)庫管理員'); INSERT INTO `role`(`id`, `name`, `nameZh`) VALUES (2, 'ROLE_admin', '系統(tǒng)管理員'); INSERT INTO `role`(`id`, `name`, `nameZh`) VALUES (3, 'ROLE_user', '普通用戶'); INSERT INTO `user_role`(`id`, `uid`, `rid`) VALUES (1, 1, 1); INSERT INTO `user_role`(`id`, `uid`, `rid`) VALUES (2, 1, 2); INSERT INTO `user_role`(`id`, `uid`, `rid`) VALUES (3, 2, 2); INSERT INTO `user_role`(`id`, `uid`, `rid`) VALUES (4, 3, 3);
2. 創(chuàng)建項目
MyBatis 靈活,JPA 便利,此處選擇前者,創(chuàng)建 Spring Boot Web 項目添加如下依賴:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency>
3. 配置數(shù)據(jù)庫
在 application.properties 中進(jìn)行數(shù)據(jù)連接配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql://localhost:3306/jpa
4. 創(chuàng)建實體類
Role
public class Role { private Integer id; private String name; private String nameZh; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getNameZh() { return nameZh; } public void setNameZh(String nameZh) { this.nameZh = nameZh; } }
User
public class User implements UserDetails { private Integer id; private String username; private String password; private Boolean enabled; private Boolean locked; private List<Role> roles; @Override public Collection<? extends GrantedAuthority> getAuthorities() { List<SimpleGrantedAuthority> authorities = new ArrayList<>(); for (Role role : roles) { authorities.add(new SimpleGrantedAuthority(role.getName())); } return authorities; } @Override public String getPassword() { return password; } @Override public String getUsername() { return username; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return !locked; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return enabled; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } public Boolean getEnabled() { return enabled; } public void setEnabled(Boolean enabled) { this.enabled = enabled; } public Boolean getLocked() { return locked; } public void setLocked(Boolean locked) { this.locked = locked; } public List<Role> getRoles() { return roles; } public void setRoles(List<Role> roles) { this.roles = roles; } }
代碼解釋:
用戶實體類需要實現(xiàn) UserDetails 接口,并且實現(xiàn)該接口中的 7 個方法
| 方法名 | 解釋 |
| — | — |
| getAuthorities() | 獲取當(dāng)前用戶對象所具有的角色信息 |
| getPassword() | 獲取當(dāng)前用戶對象的密碼 |
| getUsername() | 獲取當(dāng)前用戶對象的用戶名 |
| isAccountNonExpired() | 當(dāng)前賬號是否未過期 |
| isAccountNonLocked() | 當(dāng)前賬號是否未鎖定 |
| isCredentialsNonExpired() | 當(dāng)前賬號密碼是否未過期 |
| isEnabled() | 當(dāng)前賬號是否可用 |
用戶根據(jù)實際情況設(shè)置這 7 個方法的返回值。因為默認(rèn)情況下不需要開發(fā)者自己進(jìn)行密碼角色等信息的比對,開發(fā)者只需要提供相關(guān)信息即可,例如 getPassword() 方法返回的密碼和用戶輸入的密碼不匹配,會自動拋出 BadCredentialsException 異常,isAccountNonExpired() 方法返回了 false ,會自動拋出 AccountExpiredException 異常,因此對開發(fā)者而言,只需要按照數(shù)據(jù)庫中的數(shù)據(jù)在這里返回相應(yīng)的配置即可。此處因為數(shù)據(jù)庫中只有 enabled 和 locked 字段,故帳號未過期和密碼未過期兩個方法都返回 true
getAuthorities() 用來獲取當(dāng)前用戶所具有的角色信息,此處用戶所具有的角色存儲在 roles 屬性中,因此該方法直接遍歷 roles 屬性,然后構(gòu)造 SimpleGrantedAuthority 集合并返回
5. 創(chuàng)建UserService
@Service public class UserService implements UserDetailsService { @Autowired UserMapper userMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userMapper.loadUserByUsername(username); if (user == null) { throw new UsernameNotFoundException("賬戶不存在!"); } user.setRoles(userMapper.getUserRolesByUid(user.getId())); return user; } }
代碼解釋:
- 定義 UserService 實現(xiàn) UserDetailsService 接口,并實現(xiàn)該接口中的 loadUserByUsername 方法,該方法的參數(shù)就是用戶登錄時輸入的用戶名,通過用戶名去數(shù)據(jù)庫中查找用戶,如果沒有查詢到用戶,就拋出一個賬號不存在的異常,如果查找到了用戶,就繼續(xù)查詢該用戶所具有的角色信息,并將獲取到的 user 對象返回,再由系統(tǒng)提供的 DaoAuthenticationProvider 類去比對密碼是否正確
- loadUserByUsername 方法將在用戶登錄時自動調(diào)用
涉及到的 UserMapper 和 UserMapper.xml 如下
@Mapper public interface UserMapper { User loadUserByUsername(String username); List<Role> getUserRolesByUid(Integer id); }
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="org.sang.mapper.UserMapper"> <select id="loadUserByUsername" resultType="org.sang.model.User"> select * from user where username=#{username} </select> <select id="getUserRolesByUid" resultType="org.sang.model.Role"> select * from role r,user_role ur where r.id=ur.rid and ur.uid=#{id} </select> </mapper>
6. 配置Spring Security
@Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired UserService userService; @Bean PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userService); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/admin/**").hasRole("admin") .antMatchers("/db/**").hasRole("dba") .antMatchers("/user/**").hasRole("user") .anyRequest().authenticated() .and() .formLogin() .loginProcessingUrl("/login").permitAll() .and() .csrf().disable(); } }
此處的配置與上篇介紹的一致,唯一不同的是沒有配置內(nèi)存用戶,而是將剛剛創(chuàng)建好的 UserService 配置到 AuthenticationManagerBuilder 中。
7. 創(chuàng)建Controller
@RestController public class HelloController { @GetMapping("/admin/hello") public String admin() { return "hello admin"; } @GetMapping("/db/hello") public String dba() { return "hello dba"; } @GetMapping("/user/hello") public String user() { return "hello user"; } }
8. 測試
登錄 admin 用戶,訪問 /admin/hello,報了以下錯誤
Invalid bound statement (not found)
pom.xml 新增以下配置
<build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> <resource> <directory>src/main/resources</directory> </resource> </resources> </build>
再次登錄訪問 /admin/hello,報了以下錯誤
Caused by: org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.reflection.ReflectionException: Illegal overloaded getter method with ambiguous type for property enabled in class class org.sang.model.User. This breaks the JavaBeans specification and can cause unpredictable results.
去掉 User 實體類中 enabled 屬性的 get set 方法,如下
public class User implements UserDetails { private Integer id; private String username; private String password; private Boolean enabled; private Boolean locked; private List<Role> roles; @Override public Collection<? extends GrantedAuthority> getAuthorities() { List<SimpleGrantedAuthority> authorities = new ArrayList<>(); for (Role role : roles) { authorities.add(new SimpleGrantedAuthority(role.getName())); } return authorities; } @Override public String getPassword() { return password; } @Override public String getUsername() { return username; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return !locked; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return enabled; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } public Boolean getLocked() { return locked; } public void setLocked(Boolean locked) { this.locked = locked; } public List<Role> getRoles() { return roles; } public void setRoles(List<Role> roles) { this.roles = roles; } }
再次登錄訪問 /admin/hello
訪問 /db/hello
訪問 /user/hello
到此這篇關(guān)于SpringBoot淺析安全管理之基于數(shù)據(jù)庫認(rèn)證的文章就介紹到這了,更多相關(guān)SpringBoot數(shù)據(jù)庫認(rèn)證內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
IDEA使用JDBC安裝配置jar包連接MySQL數(shù)據(jù)庫
這篇文章介紹了IDEA使用JDBC安裝配置jar包連接MySQL數(shù)據(jù)庫的方法,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-01-01使用Spring Data JDBC實現(xiàn)DDD聚合的示例代碼
這篇文章主要介紹了使用Spring Data JDBC實現(xiàn)DDD聚合的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-09-09Sentinel結(jié)合Nacos實現(xiàn)數(shù)據(jù)持久化過程詳解
這篇文章主要介紹了Sentinel結(jié)合Nacos實現(xiàn)數(shù)據(jù)持久化過程,要持久化的原因是因為每次啟動Sentinel都會使之前配置的規(guī)則就清空了,這樣每次都要再去設(shè)定規(guī)則顯得非常的麻煩,感興趣想要詳細(xì)了解可以參考下文2023-05-05Java通過SSH連接路由器輸入命令并讀取響應(yīng)的操作方法
最近需要讀取和修改華為路由器的配置,使用Java語言開發(fā),通過SSH連接,輸入命令并讀取響應(yīng),接下來通過本文給大家介紹下Java通過SSH連接路由器,輸入命令并讀取響應(yīng),需要的朋友可以參考下2024-01-01mybatis實現(xiàn)增刪改查_動力節(jié)點Java學(xué)院整理
本文通過實例代碼給大家介紹了mybatis實現(xiàn)增刪改查功能,非常不錯,具有參考借鑒價值,需要的朋友參考下吧2017-09-09