使用SpringBoot + Spring Security进行身份验证
首先
这是一个关于使用 Spring Security 进行用户认证的帖子。
由于Spring Boot和Spring Security的版本升级,需要进行更改,因此已于2019年10月14日进行了重写。
1. 开发环境
項目名値OSWindows 10 HomejdkAdoptOpenJDKjava1.8gradle5.5.1IDEIntelliJ IDEA 2019.2.3(Community Edition)
2. 制作项目模板
(1) 创建和下载模板
在Spring Initializr中创建模板。根据链接页面,按照以下表格选择,然后点击”Generate – Ctrl + ⏎”按钮进行下载。
項目名値ProjectMaven ProjectLanguageJavaSpring Boot(SNAPSHOT)2.1.9Developer ToolsSpring Boot DevTools, LombokwebSpring WebTemplate EnginesThymeleafSecuritySpring SecuritySQLSpring Data JPA, PostgreSQL Driver
将(2)导入到IDE中。
下载文件后,将其解压到一个适当的文件夹中。然后,打开IntelliJ,选择”文件”->”打开”,然后指定解压的文件夹即可。
3. 项目文件夹和文件结构
spring-security
│ build.gradle
└─src
├─main
│ ├─java
│ │ └─com
│ │ └─example
│ │ └─security
│ │ └─springsecurity
│ │ │ ServletInitializer.java
│ │ │ SpringsecurityApplication.java
│ │ │ WebSecurityConfig.java
│ │ │
│ │ └─account
│ │ Account.java
│ │ AccountRepository.java
│ │ AccountService.java
│ │ AuthController.java
│ │
│ └─resources
│ │ application.properties
│ │ hibernate.properties
│ │
│ ├─static
│ └─templates
│ login.html
│ top.html
・・・(以下、省略)
4. 构建.gradle
只发布依赖项
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
compile('org.springframework.security:spring-security-web')
compile('org.springframework.security:spring-security-config')
compile('org.thymeleaf.extras:thymeleaf-extras-springsecurity5')
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'org.postgresql:postgresql'
annotationProcessor 'org.projectlombok:lombok'
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
}
5. Java – Java技术
我在所做的更改处留下了评论。
(1) 实体
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import javax.persistence.*;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
@Entity
@Table(name="accounts")
public class Account implements UserDetails {
private static final long serialVersionUID = 1L;
//権限は一般ユーザ、マネージャ、システム管理者の3種類とする
public enum Authority {ROLE_USER,ROLE_MANAGER, ROLE_ADMIN}
@Id
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false)
private String password;
@Column(nullable = false, unique = true)
private String mailAddress;
@Column(nullable = false)
private boolean mailAddressVerified;
@Column(nullable = false)
private boolean enabled;
@Temporal(TemporalType.TIMESTAMP)
private Date createdAt;
// roleは複数管理できるように、Set<>で定義。
@ElementCollection(fetch = FetchType.EAGER)
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private Set<Authority> authorities;
// JPA requirement
protected Account() {}
//コンストラクタ
public Account(String username, String password, String mailAddress) {
this.username = username;
this.password = password;
this.mailAddress = mailAddress;
this.mailAddressVerified = false;
this.enabled = true;
this.authorities = EnumSet.of(Authority.ROLE_USER);
}
//登録時に、日時を自動セットする
@PrePersist
public void prePersist() {
this.createdAt = new Date();
}
//admin権限チェック
public boolean isAdmin() {
return this.authorities.contains(Authority.ROLE_ADMIN);
}
//admin権限セット
public void setAdmin(boolean isAdmin) {
if (isAdmin) {
this.authorities.add(Authority.ROLE_MANAGER);
this.authorities.add(Authority.ROLE_ADMIN);
} else {
this.authorities.remove(Authority.ROLE_ADMIN);
}
}
//管理者権限を保有しているか?
public boolean isManager() {
return this.authorities.contains(Authority.ROLE_MANAGER);
}
//管理者権限セット
public void setManager(boolean isManager) {
if (isManager) {
this.authorities.add(Authority.ROLE_MANAGER);
} else {
this.authorities.remove(Authority.ROLE_MANAGER);
this.authorities.remove(Authority.ROLE_ADMIN);
}
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorities = new ArrayList<>();
for (Authority authority : this.authorities) {
authorities.add(new SimpleGrantedAuthority(authority.toString()));
}
return authorities;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getMailAddress() {
return mailAddress;
}
public void setMailAddress(String mailAddress) {
this.mailAddress = mailAddress;
}
public boolean isMailAddressVerified() {
return mailAddressVerified;
}
public void setMailAddressVerified(boolean mailAddressVerified) {
this.mailAddressVerified = mailAddressVerified;
}
public Date getCreatedAt() {
return createdAt;
}
}
(2) 存储库
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface AccountRepository extends CrudRepository<Account, Long> {
public Account findByUsername(String username);
}
(3) 服务
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class AccountService implements UserDetailsService {
@Autowired
private AccountRepository repository;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public Account loadUserByUsername(String username) throws UsernameNotFoundException {
if (username == null || "".equals(username)) {
throw new UsernameNotFoundException("Username is empty");
}
Account user = repository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found: " + username);
}
return user;
}
//adminを登録するメソッド
@Transactional
public void registerAdmin(String username, String password, String mailAddress) {
Account user = new Account(username, passwordEncoder.encode(password), mailAddress);
user.setAdmin(true);
repository.save(user);
}
//管理者を登録するメソッド
@Transactional
public void registerManager(String username, String password, String mailAddress) {
Account user = new Account(username, passwordEncoder.encode(password), mailAddress);
user.setManager(true);
repository.save(user);
}
//一般ユーザを登録するメソッド
@Transactional
public void registerUser(String username, String password, String mailAddress) {
Account user = new Account(username, passwordEncoder.encode(password), mailAddress);
repository.save(user);
}
}
(4) 控制器
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class AuthController {
@RequestMapping("/")
public String index() {
return "redirect:/top";
}
@GetMapping("/login")
public String login() {
return "login";
}
@PostMapping("/login")
public String loginPost() {
return "redirect:/login-error";
}
@GetMapping("/login-error")
public String loginError(Model model) {
model.addAttribute("loginError", true);
return "login";
}
@RequestMapping("/top")
public String top() {
return "/top";
}
}
(5)安全配置
import com.example.security.springsecurity.account.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AccountService userService;
@Override
protected void configure(HttpSecurity http) throws Exception {
//TODO: 最低限の実装。cssなどのstaticファイルなどの許可を追加する必要あります。
http
.authorizeRequests()
.antMatchers("/login", "/login-error").permitAll()
.antMatchers("/**").hasRole("USER")
.and()
.formLogin()
.loginPage("/login").failureUrl("/login-error");
}
//変更点 ロード時に、「admin」ユーザを登録する。
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userService)
.passwordEncoder(passwordEncoder());
//TODO: propertyでadmin情報は管理しましょう。
userService.registerAdmin("admin", "secret", "admin@localhost");
}
//変更点 PasswordEncoder(BCryptPasswordEncoder)メソッド
@Bean
public PasswordEncoder passwordEncoder() {
//
return new BCryptPasswordEncoder();
}
}
6. 资源 (zī
(1) 登录页.html yè.html)
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8" />
<title>Login page</title>
<style>
.alert-danger{color:red;}
</style>
</head>
<body>
<h2>ログイン画面</h2>
<form th:action="@{/login}" method="post">
<div th:if="${session['SPRING_SECURITY_LAST_EXCEPTION']} != null"
class="alert-danger">
<span th:text="ユーザ名またはパスワードに誤りがあります"></span>
</div>
<div style="width:160px;"><label for="username">ユーザ名:</label></div>
<input type="text" name="username" autofocus="autofocus" />
<br/>
<div style="width:160px;"><label for="password">パスワード:</label></div>
<input type="password" name="password" />
<br/>
<p><input type="submit" value="ログイン" /></p>
</form>
</body>
</html>
(2) 首页.html .html)
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<meta charset="UTF-8" />
<title>メニュー画面</title>
</head>
<body>
<h2>こんにちは</h2>
<div th:fragment="logout" sec:authorize="isAuthenticated()">
<p>こんにちは:
<span sec:authentication="name"></span>さん</p>
<p>mail:
<span sec:authentication="principal.mailAddress"></span></p>
<p>権限:
<span sec:authentication="principal.authorities"></span></p>
<form action="#" th:action="@{/logout}" method="post">
<input type="submit" value="ログアウト" />
</form>
</div>
</body>
</html>
(3) 应用程序配置文件.properties
spring.datasource.driverClassName=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/sampledb
spring.datasource.username=testuser
spring.datasource.password=secret
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.format_sql=true
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
(4) hibernate.properties
(4)hibernate.properties文件
如果要连接到PostgreSQL,请在此配置文件中进行追加设置。
hibernate.jdbc.lob.non_contextual_creation = true
7. 检查行动是否正常
(1) 运行 Spring Boot
在IntelliJ中选择「View」→「Tool Windows」→「Gradle」,然后打开「Gradle」窗口。
在「Gradle」窗口中选择「Tasks」→「application」,然后双击「bootRun」。
(2) 行动确认
访问http://localhost:8080。登录页面将显示如下,并在认证后显示首页。
参考网站:
《粉红技术》尝试使用Spring Security实施用户认证