Spring Security入門例項
目錄
1.1 appliacition.properties的編寫
2.4.1 SecurityConfiguration的編寫
3.1.1 There is no PasswordEncoder mapped for the id “null”
參考:There is no PasswordEncoder mapped for the id “null”
3.1.2 Encoded password does not look like BCrypt
前言:這個通過SpringSecurity實現了一個許可權控制訪問,擁有指定許可權的使用者才能訪問指定的網頁.
SpringBoot的版本為2.1.1,springSecurity的版本為5.1.2,前端用的是thymeleaf.
第一章 程式架構
config存放了配置檔案,controller就是傳統上的控制頁面跳轉的,domain裡面裝的是資料模型,dao就是SpringJPA那一套,service裡放了一個自己實現的UserDetailsService,test用來測試一些東西
1.1 appliacition.properties的編寫
這是複製的以前的專案的一個properties,和此專案相關性不大,只是為了能用省事,可根據自己的情況來配置
spring.datasource.url=jdbc:mysql://localhost:3306/testSpringSecurity?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&useSSL=true spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.thymeleaf.prefix=classpath:/templates/ spring.jpa.open-in-view=true spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true spring.jpa.show-sql = true spring.jpa.hibernate.ddl-auto=update spring.thymeleaf.cache=false logging.level.org.springframework.data=DEBUG server.servlet.session.timeout=3600 server.servlet.context-path=/SpringSecurity spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
第二章 程式碼編寫
2.1 domain層的編寫
SysUser與SysRole是多對多的關係
2.1.1 SysUser
實現UserDetails是因為需要一個實現此介面的類來提供許可權等資訊.
@Entity
public class SysUser implements UserDetails{
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@Column
private String username;
@Column
private String password;
@ManyToMany(cascade= {CascadeType.REFRESH},fetch=FetchType.EAGER)
private List<SysRole> roles;
public SysUser() {
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
System.out.println("進入了cellection");
List<GrantedAuthority> auths = new ArrayList<GrantedAuthority>();
List<SysRole> roles=this.getRoles();
for(SysRole role:roles)
{
auths.add(new SimpleGrantedAuthority(role.getName()));
}
return auths;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
//省略get,set...
}
2.1.2 SysRole
@Entity
public class SysRole {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@Column
private String name;
//省略get,set...
}
2.1.3 Msg
這個domain主要是我們為了測試一下controller與themeleaf執行的正確性
public class Msg {
private String title;
private String content;
private String etraInfo;
public Msg(String title,String conten,String etraInfo) {
super();
this.title=title;
this.content = conten;
this.etraInfo = etraInfo;
}
//省略get,set...
}
2.2 dao層的編寫
只要能根據使用者名稱查詢使用者即可
public interface SysUserRepository extends JpaRepository<SysUser,Long>{
SysUser findByUsername(String username);
}
2.3 service層的編寫
SpringSecurity將呼叫實現了UserDetailService的類返回User
@Component
public class myUserDetailService implements UserDetailsService {
@Autowired
SysUserRepository sysUserRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser user = sysUserRepository.findByUsername(username);
if(user==null) {
System.out.println("沒有查詢到使用者");
}
return user;
}
}
2.4 config的編寫
2.4.1 SecurityConfiguration的編寫
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
//這裡將我們剛才寫的myUserDetailsService,將呼叫它返回user
@Bean
protected UserDetailsService myUserDetailsService() {
return new myUserDetailService();
}
//將加密器註冊為BEAN
@Bean
protected PasswordEncoder PasswordEncoder() {
return new BCryptPasswordEncoder();
}
//這是新版SpringSecurity的要求,要求必須指定加密器,不然會報null的錯誤,這裡我們表示將用
//BCryptPasswordEncoder()來加密解密密碼.
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService()).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() //給請求授權,定義哪些URL需要被保護、哪些不需要被保護
.antMatchers("/test1/**").hasRole("ADMIN")
.antMatchers("/test2/**").hasRole("USER")
.antMatchers("/test3/**").hasRole("TEACHER")
.anyRequest().authenticated() //任何沒有匹配上的其他的url請求,都需要使用者被驗證
.and()
.formLogin() //定義登陸的一些事項
.loginPage("/login") // 定義當需要使用者登入時候,轉到的登入頁面
.failureUrl("/login?error")
.permitAll()
.and()
.logout().permitAll();
}
}
2.4.2 WebMvcConfig的編寫
這裡就配置了一個頁面跳轉,和再controller配置效果是一樣的.
@Configuration
public class WebMvcConfig implements WebMvcConfigurer{
@Override
public void addViewControllers(ViewControllerRegistry registry) {
WebMvcConfigurer.super.addViewControllers(registry);
registry.addViewController("/login").setViewName("login");
}
}
2.5 controller的編寫
這裡登陸成功會自動跳轉到主頁.
@Controller
public class HomeController {
@RequestMapping("/")
public String index(Model model) {
Msg msg = new Msg("測試標題", "測試內容", "額外資訊,只對管理員顯示");
model.addAttribute("msg", msg);
return "home";
}
@RequestMapping("/test1")
public String test1(Model model) {
Msg msg = new Msg("測試標題", "測試內容", "額外資訊,只對管理員顯示");
model.addAttribute("msg", msg);
return "test1";
}
@RequestMapping("/test2")
public String test2(Model model) {
Msg msg = new Msg("測試標題", "測試內容", "額外資訊,只對管理員顯示");
model.addAttribute("msg", msg);
return "test2";
}
@RequestMapping("/test3")
public String test3(Model model) {
Msg msg = new Msg("測試標題", "測試內容", "額外資訊,只對管理員顯示");
model.addAttribute("msg", msg);
return "test3";
}
}
2.6 temeplates的書寫
home:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
<meta content="text/html;charset=UTF-8">
<title sec:authentication="name"></title>
</head>
<body>
<a th:href="@{/}">首頁</a>
<h1 th:text="${msg.title}"></h1>
<p th:text="${msg.content}"></p>
<!--這裡本來是想測試下thymeleaf的許可權功能,但是沒成功,但這不影響我們測試頁面的訪問功能 -->
<div sec:authorize="isAuthenticated()">
<p th:text="${msg.etraInfo}"></p>
</div>
<span sec:authorize="hasRole('ROLE_ADMIN')">管理員</span>
<div sec:authorize="hasRole('ROLE_USER')">
<p >無更多資訊顯示</p>
</div>
<form th:action="@{/logout}" method="post">
<input type="submit" value="登出"/>
</form>
</body>
</html>
test1:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>這是test1</title>
</head>
<body>
<p th:text="${msg.content}"></p>
</body>
</html>
test2:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>這是test2</title>
</head>
<body>
<p th:text="${msg.content]"></p>
</body>
</html>
test3:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>這是test3</title>
</head>
<body>
<p th:text="${msg.content}"></p>
</body>
</html>
login:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta content="text/html;charset=UTF-8"/>
<title>登陸頁面</title>
</head>
<body>
<a th:href="@{/}">首頁</a>
<p th:if="${param.logout}">已成功登出</p>
<p th:if="${param.error}">有錯誤,請重試</p>
<form th:action="@{/login}" method="POST">
<label>賬號</label>
<input type="text" name="username">
<label>密碼</label>
<input type="password" name="password">
<input type="submit" id="login" value="login"/>
</form>
</body>
</html>
第三章 操作之前的準備
3.1 執行的時候可能出現的錯誤
3.1.1 There is no PasswordEncoder mapped for the id “null”
參考:There is no PasswordEncoder mapped for the id “null”
就是沒用類似BCrypt的加密器,現在這個版本的必須要用這個加密,按上面的加個BCrypt的Bean就好了.
3.1.2 Encoded password does not look like BCrypt
參考:https://blog.csdn.net/zhuyongru/article/details/82108543
就是資料庫的密碼不是加密過的,因為我們設定了加密器,所以加密器要求我們資料的密碼是加密過的,具體的形式如下圖.
如何插入加密後的密碼?見下圖,將使用者的密碼取出並加密
@SpringBootTest
@RunWith(SpringRunner.class)
public class MyTest {
@Autowired
SysUserRepository sysUserRepository;
@Test
public void test1() {
System.out.println("第一個測試");
SysUser user = sysUserRepository.findByUsername("zhangchen");
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
user.setPassword(passwordEncoder.encode(user.getPassword()));
sysUserRepository.save(user);
}
}
3.2 資料庫的配置
sys_role
注意,資料庫裡的許可權我們要加上ROLE_
sys_user
sys_user_roles
第四章 具體的操作
開啟登陸頁面
此時我們發現無論訪問什麼url都會跳轉到登陸,於是我們輸入賬號密碼進行登陸,登陸後跳轉到首頁
我們嘗試訪問/test1
再嘗試訪問/test2,可以訪問被禁止了,因為沒有USER許可權
我們加上USER許可權,再重新登陸,再訪問test2,成功了