technology-note/java/springboot系列/springsecurity/springboot+security整合1.md
2019-02-23 16:59:40 +08:00

290 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
id: "2018-08-20-10-38"
date: "2018-08-20-10-38"
title: "springboot+security整合1"
tags: ["java", "spring","springboot","spring-security","security"]
categories:
- "java"
- "spring boot学习"
---
**说明 springboot 版本 2.0.3<br/>源码地址:[点击跳转](https://github.com/FleyX/demo-project/tree/master/springboot_spirngsecurity_demo)**
## 一、 介绍
&emsp;&emsp;Spring Security 是一个能够为基于 Spring 的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在 Spring 应用上下文中配置的 Bean充分利用了 Spring IoCDI控制反转 Inversion of Control ,DI:Dependency Injection 依赖注入)和 AOP面向切面编程功能为应用系统提供声明式的安全访问控制功能减少了为企业系统安全控制编写大量重复代码的工作。
## 二、 环境搭建
&emsp;&emsp;建立 springboot2 项目,加入 security 依赖,mybatis 依赖
<!-- more -->
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</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>
```
数据库为传统的用户--角色--权限,权限表记录了 url 和 methodspringboot 配置文件如下:
```yml
mybatis:
type-aliases-package: com.example.demo.entity
server:
port: 8081
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=true
username: root
password: 123456
http:
encoding:
charset: utf-8
enabled: true
```
springboot 启动类中加入如下代码,设置路由匹配规则。
```java
@Override
protected void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(false) //设置路由是否后缀匹配,譬如/user能够匹配/user.,/user.aa
.setUseTrailingSlashMatch(false); //设置是否后缀路径匹配,比如/user能够匹配/user,/user/
}
```
## 三、 security 配置
&emsp;&emsp;默认情况下 security 是无需任何自定义配置就可使用的,我们不考虑这种方式,直接讲如何个性化登录过程。
#### 1、 建立 security 配置文件,目前配置文件中还没有任何配置。
```java
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
```
#### 2、 个性化登录security 中的登录如下:
![登录过程](https://raw.githubusercontent.com/FleyX/files/master/blogImg/springsecurity/20190107101026.png)
- security 需要一个 user 的实体类实现`UserDetails`接口,该实体类最后与系统中用户的实体类分开,代码如下:
```java
public class SecurityUser implements UserDetails{
private static final long serialVersionUID = 1L;
private String password;
private String name;
List<GrantedAuthority> authorities;
public SecurityUser(string name,string password) {
this.id = id;
this.password = password;
this.name = name;
this.age = age;
}
public void setAuthorities(List<GrantedAuthority> authorities) {
this.authorities = authorities;
}
@Override
public Collection<GrantedAuthority> getAuthorities() {
return this.authorities;
}
@Override //获取校验用户名
public String getUsername() {
return String.valueOf(this.id);
}
@Override //获取校验用密码
public String getPassword() {
return password;
}
@Override //账户是否未过期
public boolean isAccountNonExpired() {
// TODO Auto-generated method stub
return true;
}
@Override //账户是否未锁定
public boolean isAccountNonLocked() {
// TODO Auto-generated method stub
return true;
}
@Override //帐户密码是否未过期,一般有的密码要求性高的系统会使用到,比较每隔一段时间就要求用户重置密码
public boolean isCredentialsNonExpired() {
// TODO Auto-generated method stub
return true;
}
@Override //账户是否可用
public boolean isEnabled() {
// TODO Auto-generated method stub
return true;
}
}
```
- 编写了实体类还需要编写一个服务类 SecurityService 实现`UserDetailsService`接口,重写 loadByUsername 方法,通过这个方法根据用户名获取用户信息,代码如下:
```java
@Component
public class SecurityUserService implements UserDetailsService {
@Autowired
private JurisdictionMapper jurisdictionMapper;
@Autowired
private UserMapper userMapper;
private Logger log = LoggerFactory.getLogger(this.getClass());
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("登录用户id为{}",username);
int id = Integer.valueOf(username);
User user = userMapper.getById(id);
if(user==null) {
//抛出错误,用户不存在
throw new UsernameNotFoundException("用户名 "+username+"不存在");
}
//获取用户权限
List<GrantedAuthority> authorities = new ArrayList<>();
List<Jurisdiction> jurisdictions = jurisdictionMapper.selectByUserId(id);
for(Jurisdiction item : jurisdictions) {
GrantedAuthority authority = new MyGrantedAuthority(item.getMethod(),item.getUrl());
authorities.add(authority);
}
SecurityUser securityUser = new SecurityUser(user.getName(),user.getPassword(),authority):
user.setAuthorities(authorities);
return securityUser;
}
}
```
- 通常我们会对密码进行加密,所有还要编写一个 passwordencode 类,实现 PasswordEncoder 接口,代码如下:
```java
@Component
public class MyPasswordEncoder implements PasswordEncoder {
private Logger log = LoggerFactory.getLogger(this.getClass());
@Override //不清楚除了在下面方法用到还有什么用处
public String encode(CharSequence rawPassword) {
return StringUtil.StringToMD5(rawPassword.toString());
}
//判断密码是否匹配
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return encodedPassword.equals(this.encode(rawPassword));
}
}
```
#### 3、 编辑配置文件
- 编写 config Bean 以使用上面定义的验证逻辑,securityUserService、myPasswordEncoder 通过@Autowired 引入。
```java
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(securityUserService)
.passwordEncoder(myPasswordEncoder);
}
```
- 然后编写 configure Bean和上一个不一样参数不同实现 security 验证逻辑,代码如下:
```java
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf() //跨站
.disable() //关闭跨站检测
.authorizeRequests()//验证策略策略链
.antMatchers("/public/**").permitAll()//无需验证路径
.antMatchers("/login").permitAll()//放行登录
.antMatchers(HttpMethod.GET, "/user").hasAuthority("getAllUser")//拥有权限才可访问
.antMatchers(HttpMethod.GET, "/user").hasAnyAuthority("1","2")//拥有任一权限即可访问
//角色类似hasRole(),hasAnyRole()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/public/unlogin") //未登录跳转页面,设置了authenticationentrypoint后无需设置未登录跳转页面
.loginProcessingUrl("/public/login")//处理登录post请求接口无需自己实现
.successForwardUrl("/success")//登录成功转发接口
.failureForwardUrl("/failed")//登录失败转发接口
.usernameParameter("id") //修改用户名的表单name默认为username
.passwordParameter("password")//修改密码的表单name默认为password
.and()
.logout()//自定义登出
.logoutUrl("/public/logout") //自定义登出api无需自己实现
.logoutSuccessUrl("public/logoutSuccess")
}
```
到这里便可实现 security 与 springboot 的基本整合。
## 四、实现记住我功能
#### 1、 建表
&emsp;&emsp;记住我功能需要数据库配合实现,首先要在数据库建一张表用户保存 cookie 和用户名,数据库建表语句如下:不能做修改
```sql
CREATE TABLE `persistent_logins` (
`username` varchar(64) NOT NULL,
`series` varchar(64) NOT NULL,
`token` varchar(64) NOT NULL,
`last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`series`)
)
```
#### 2、 编写 rememberMeservice Bean
&emsp;&emsp;代码如下:
```java
@Bean
public RememberMeServices rememberMeServices(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
PersistentTokenBasedRememberMeServices rememberMeServices =
new PersistentTokenBasedRememberMeServices("INTERNAL_SECRET_KEY",securityUserService,jdbcTokenRepository);
//还可设置许多其他属性
rememberMeServices.setCookieName("kkkkk"); //客户端cookie名
return rememberMeServices;
}
```
dataSource 为@Autowired 引入
#### 3、 配置文件设置 remember
&emsp;&emsp;在 config(HttpSecurity http)中加入记住我功能
```java
.rememberMe()
.rememberMeServices(rememberMeServices())
.key("INTERNAL_SECRET_KEY")
```
在登录表单中设置 remember-me 即可实现记住我功能。