Failed to convert property value of type ‘null‘ to required type ‘double‘ for property ‘balance‘
技術標籤:Java學習資料庫spring bootmybatisshiroSpringSecurity
1、前言
之前寫過一篇SSM整合SpringSecurity,沒看過的可以看看,可以發現在SSM框架裡整合Security框架是很繁瑣的,所以很多人選擇用Shiro搭配SSM使用。而在SpringBoot中,這個情況就不一樣了,如果說是簡單使用,只需要在SpringBoot專案中加入Security的依賴就可以了,不需要寫什麼其他的東西。
2、簡單使用
首先建一個SpringBoot專案,在pom檔案中加入以下依賴:
<dependency>
<groupId >org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
為了看Security是否已被整合進SpringBoot,可以編寫一個HelloController,如下:
@Controller
public class HelloController {
@ResponseBody
@GetMapping("/hello")
public String hello(){
return "hello";
}
}
再接下來就可以啟動專案了,在瀏覽器中訪問localhost:8080/hello,此時會看到如下頁面:
這裡使用者名稱為user,密碼會在啟動專案時列印到控制檯,登入進去即可看到hello字串。
3、進階使用
我們使用SpringSecurity不可能每次都去重新生成一個密碼,所以有以下幾種策略來對帳戶密碼進行配置。
3.1 配置檔案
在application.properties檔案中,加入以下程式碼即可自行配置SpringSecurity的帳戶密碼:
spring.security.user.name=sang
spring.security.user.password=123
spring.security.user.roles=admin
這下啟動程式後,就不會再生成一個密碼了,登入賬戶就變成了sang,密碼就變成了123,同時這個賬戶的角色是admin。
3.2 配置類
要是還是嫌配置檔案配置的東西不夠多,還可以使用配置類進行配置,首先新建一個類,比如叫MyWebSecurityConfig,這個類需要繼承WebSecurityConfigurerAdapter,其程式碼如下:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder(){
// 加密演算法
return new BCryptPasswordEncoder(10);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 在記憶體中配置賬號密碼
auth.inMemoryAuthentication()
// 賬號
.withUser("admin")
// 密碼【此處需要將密碼加密】
.password(passwordEncoder().encode("123"))
// 賬號的角色
.roles("ADMIN")
.and()
// 賬號
.withUser("user")
// 密碼【此處需要將密碼加密】
.password(passwordEncoder().encode("123"))
// 賬號的角色
.roles("USER")
;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// 配置admin目錄下的所有頁面都需要ADMIN角色才能訪問
.antMatchers("/admin/**").hasRole("ADMIN")
// 其他請求都需要驗證許可權
.anyRequest().authenticated()
.and()
// 開啟表單登入
.formLogin()
// 登入處理URL
.loginProcessingUrl("/login")
// 允許登入頁和登入處理連結不經過許可權驗證
.permitAll()
.and()
// 關閉csrf
.csrf().disable()
;
}
}
程式碼的解釋都在註釋裡,接下來在HelloController中新增一個函式:
@ResponseBody
@GetMapping("/admin/hello")
public String adminHello(){
return "hello admin";
}
啟動程式,訪問localhost:8080/hello,在登入頁面嘗試登入admin和user兩個賬號,都可以訪問hello,而訪問localhost:8080/admin/hello,則會發現只有admin賬戶才可訪問,user賬戶沒有許可權。
3.3 資料庫
像這種在記憶體中配置帳戶密碼的操作,在實際中並不實用,因為不可能每加個賬戶,就該程式碼並重啟服務,所以要把資料庫也整合進去,有關於資料庫的整合,我寫過一篇SpringBoot整合Mybatis的部落格,不會的可以看一下。
首先我們需要從資料庫中將資料拿出來,那麼需要定義個實體類物件,用於接收拿到的資料,實體類有兩個,一個是User,對應賬戶,一個是Role,對應角色:
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 String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", enabled=" + enabled +
", locked=" + locked +
", roles=" + roles +
'}';
}
public User() {
}
public User(Integer id, String username, String password, Boolean enabled, Boolean locked, List<Role> roles) {
this.id = id;
this.username = username;
this.password = password;
this.enabled = enabled;
this.locked = locked;
this.roles = 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 void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
public void setLocked(Boolean locked) {
this.locked = locked;
}
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
}
注意User類實現了UserDetails介面,這是為了跟Security配合使用,以及User類中有個Role的列表,是為了在Security獲取使用者相關資訊時,將使用者的角色一起帶過去。
Role:
public class Role {
private Integer id;
private String name;
private String nameZh;
@Override
public String toString() {
return "Role{" +
"id=" + id +
", name='" + name + '\'' +
", nameZh='" + nameZh + '\'' +
'}';
}
public Role() {
}
public Role(Integer id, String name, String nameZh) {
this.id = id;
this.name = name;
this.nameZh = 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;
}
}
有了實體類,接下來就是Mapper,也就是Dao層,首先是UserMapper介面:
@Mapper
public interface UserMapper {
User loadUserByUsername(String username);
List<Role> getUserRolesByUid(Integer id);
}
其對應的UserMapper.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTC Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.alageek.study.mapper.UserMapper">
<select id="loadUserByUsername" parameterType="string" resultType="com.alageek.study.entity.User">
select * from user where username = #{username}
</select>
<select id="getUserRolesByUid" parameterType="integer" resultType="com.alageek.study.entity.Role">
select role.* from role, user_role where user_role.uid = #{id} and user_role.rid = role.id
</select>
</mapper>
然後在service層定義UserService:
@Service
public class UserService implements UserDetailsService {
@Autowired
UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
User user = userMapper.loadUserByUsername(s);
if(user == null){
throw new UsernameNotFoundException("賬戶不存在");
}
user.setRoles(userMapper.getUserRolesByUid(user.getId()));
return user;
}
}
與User一樣,為了配合Security使用,UserService需要實現介面UserDetailsService,其程式碼先根據使用者名稱s獲取到使用者資訊,再通過使用者id獲取到相關角色,再將組合後的使用者資訊返回。
有了Service後,還需要在Security的配置類中進行相關配置,程式碼如下:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}
在配置類中加入上述程式碼即可,就能從資料庫中獲取賬戶資料進行比對,從而登入,而註冊只需往資料庫中插入資料即可。
寫完上述資料庫相關程式碼後,測試功能跟上面一樣,寫一個Controller啟動專案登入下試試即可。
4、相關資源
5、博主的話
原始碼沒有跟部落格說的一模一樣,不過我覺得能看到這裡的小夥伴,借鑑應該是沒有問題的,加油,一起學習吧,有什麼問題請在評論說出來。