在Spring工程中经常会把一些常用的功能打包成一个模块提供给大家使用,SpringSecurity便是针对Web安全打包的一个模块,提供了一些基本比如身份验证、授权、Web安全等等代码。
下面会演示一个用户登陆的例子,应用设计三个页面 – home.html
,hello.html
,login.html
,其中hello.html
需要登陆之后才能访问,如果用户点击了 home.html
中的链接会先重定向到 login.html
。
构建不安全的页面
在演示Spring提供的安全模块之前,需要先构建一个不安全的网站,这个网站提供的任何页面都可以随意的访问。
- 工程结构,三部分:Gradle构建文件,Java代码,HTML资源文件
➜ spring-securing-web tree .
.
├── build.gradle
└── src
└── main
├── java
│ └── hello
│ ├── App.java
│ └── MvcConfig.java
└── resources
└── templates
├── hello.html
└── home.html
- 构建文件,和之前系列一致,仅仅在依赖地方有变化
dependencies {
compile("org.springframework.boot:spring-boot-starter-thymeleaf")
testCompile("junit:junit")
}
- Java代码非常简单,
App.java
提供App运行入口,MvcConfig.java
用来注册URL和页面的映射关系。
// App.java
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class);
}
}
// MvcConfig.java
@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/home").setViewName("home");
registry.addViewController("/").setViewName("home");
registry.addViewController("/hello").setViewName("hello");
registry.addViewController("/login").setViewName("login");
}
}
- HTML资源文件为两个静态页面
// home.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example</title>
</head>
<body>
<h1>Welcome!</h1>
<p>Click <a th:href="@{/hello}">here</a> to see a greeting.</p>
</body>
</html>
// hello.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Hello World!</title>
</head>
<body>
<h1>Hello world!</h1>
</body>
</html>
- 此时运行
App.java
中的main()
方法,可以访问http://localhost:8080/home
,并从home
页的连接跳转到hello.html
添加安全的授权登陆
接下来会利用SpringSecurity模块提供的安全访问功能,把未授权用户的访问重定向到 login.html
。
- 添加依赖:
spring-boot-starter-security
dependencies {
...
compile("org.springframework.boot:spring-boot-starter-security")
...
}
- 添加授权登陆配置 –
WebSecurityConfig.java
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
}
- 添加HTML页面 –
login.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example </title>
</head>
<body>
<div th:if="${param.error}">
Invalid username and password.
</div>
<div th:if="${param.logout}">
You have been logged out.
</div>
<form th:action="@{/login}" method="post">
<div><label> User Name : <input type="text" name="username"/> </label></div>
<div><label> Password: <input type="password" name="password"/> </label></div>
<div><input type="submit" value="Sign In"/></div>
</form>
</body>
</html>
- 在 App.java 中运行 main() 方法,这时候再访问
hello.html
会被跳转到login.html
, 使用user
和password
分别作为用户名密码登陆后才可以正常访问。
安全模块的关键在于 WebSecurityConfig.java
类的配置,@EnableWebSecurity
注解会启动Spring的安全模块,并提供对SpringMVC的支持。configure(HttpSecurity)
指定了那些页面应该被保护那些不是,并指定了登陆/登出页面。configureGlobal(AuthenticationManagerBuilder)
方法在内存中创建了一个授权用户/密码。
当用户访问了授权页面时,会被重定向到 login.html
,它提供了表单入口,用户填写用户名/密码后会发送POST请求给 /login
,Spring会自动处理对于 /login
的POST请求,并返回是否登陆成功。
使用UserDetailsService
在Spring安全模块中,包含一个基本的概念 – 用户和赋予的权利。在前一节中,通过
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
在内存中创建了一个用户。但是对于大部分应用来说都会有自己的用户系统,Spring通过UserDetailsService方便开发者定义自己的身份验证逻辑。UserDetailsService
的职责非常简单:给一个用户名,返回一个UserDetails
实现,UserDetails
会回答这些问题:用户的合法性、用户名/密码、用户的权利(org.springframework.security.core.GrantedAuthority),接下来的事情继续交给SpringBoot处理。
为了演示UserDetailsService,需要创建一个用户(Account)对象和一段存储逻辑,以便把用户信息持久化。
// Account.java 用户模型
@Entity
public class Account {
@Id
@GeneratedValue
private Long id;
@JsonIgnore
private String password;
private String username;
public Account(){}
public Account(String username, String password) {
this.username = username;
this.password = password;
}
public Long getId() {
return id;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
}
// AccountRepository.java 存储用户信息的Repository
public interface AccountRepository extends JpaRepository<Account, Long> {
Account findByUsername(String name);
}
重新实现WebSecurityConfig.java
中的身份信息,在这里通过 AccountRepository
读取保存在数据库中的用户信息来验证用户.
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AccountRepository accountRepository;
@Override
protected void configure(HttpSecurity http) throws Exception {
....
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(buildUserDetailsService());
}
@Bean
UserDetailsService buildUserDetailsService() {
return username -> {
Account account = accountRepository.findByUsername(username);
User user = new User(account.getUsername(), account.getPassword(),
true, true, true, true,
AuthorityUtils.createAuthorityList("USER", "write"));
return user;
};
}
}
这里的关键在于添加了自己的 UserDetailsService 实现,并在这里赋予用户的权利。接下来需要在数据库中临时添加一些用户数据:
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class);
}
// 伪造一些数据
@Bean
CommandLineRunner init(AccountRepository accountRepository) {
return (args) -> {
List<String> names = Arrays.asList(
"ntop,lilei,hanmeimei".split(","));
for(String name: names) {
accountRepository.save(new Account(name, "password"));
}
};
}
}
接下来可以运行main()
方法,尝试用 “ntop:password” 登陆网页了。