授权验签
# 核心模块
# spring security
# 1.引入spring security
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2
3
4
# 2.定义AuthenticationManager
创建
SecurityConfigurer
核心配置,实现WebSecurityConfigurerAdapter
方法。
Spring Security
中,接口AuthenticationManager
用于抽象建模认证管理器,用于处理一个认证请求,也就是Spring Security
中的Authentication
认证令牌。
AuthenticationManager
在认证过程中必须按以下顺序处理以下认证异常AuthenticationException
:
DisabledException
– 账号被禁用时抛出LockedException
– 账号被锁定时抛出BadCredentialsException
– 密码错误时抛出
Spring Security
框架提供了AuthenticationManager
的缺省实现ProviderManager
。
ProviderManager
管理了多个身份管理源,或者叫做认证提供者AuthenticationProvider
,用于认证用户。
它自身不实现身份验证,而是逐一使用认证提供者进行认证,直到某一个认证提供者能够成功地验证该用户的身份
(或者是已经尝试完了该集合中所有的认证提供者仍然不能认证该用户的身份)。
通过ProviderManager
,Spring Security
能够为单个应用程序提供多种认证机制。
AuthenticationManager
会在Spring Security
应用配置阶段被构建,比如被某个WebSecurityConfigurerAdapter
构建,
然后在工作阶段被使用。比如一个基于用户名密码认证机制的Spring Web MVC + Spring Security
应用,应用/容器启动过程中,
AuthenticationManager
构建后会被设置到基于用户名密码进行认证的安全过滤器UsernamePasswordAuthenticationFilter
上,缺省情况下,
当请求为访问地址/login
的POST
请求时,UsernamePasswordAuthenticationFilter
就会认为这是一个用户认证请求,从而获取请求中的用户名/密码信息,
使用AuthenticationManager
认证该请求用户的身份。
这里配置如下:
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {
@Bean(name = BeanIds.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
2
3
4
5
6
7
# 3.使用BCrypt
给密码加密
spring boot
包含了很多种密码编码器,有 ldap
、MD4
、 MD5
、noop
、pbkdf2
、scrypt
、SHA-1
、SHA-256
,但是默认使用的是BCryptPasswordEncoder
来加密。
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {
@Bean(name = BeanIds.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# 为什么不用
MD5
来加密?目前很大一部分存在安全问题的系统一般仅仅使用密码的 MD5 值进行保存,可以通过 MD5 查询库去匹配对大部分的密码(可以直接从彩虹表里反推出来), 而且 MD5 计算 Hash 值碰撞容易构造,安全性大大降低。 MD5 加盐在本地计算速度也是很快,但是密码短也是极其容易破解; 因此更好的选择是
SHA-256
、BCrypt
等,所以spring boot
官方推荐BCrypt
# 4.使用自定义数据库来安全认证
如果不配置自定义数据库安全认证,spring security
在启动的时候会在内存生成一个随机密码,账号默认为admin
,但是一般的系统,
登录用户都是持久化在数据库中,在执行安全认证时候会与数据库中的账号和密码进行比对。
这里的
userDetailService
需要实现userDetailService
接口
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 加入自定义的安全认证
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
@Bean(name = BeanIds.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 5.配置拦截链和登录逻辑
通过重写config
来实现spring security
的核心配置,这里增加一个逻辑,去除所有配置过的url校验逻辑。完整的SecurityConfigurer
类如下:
package com.zeal.zealsay.security;
import com.zeal.zealsay.config.FilterIgnorePropertiesConfig;
import com.zeal.zealsay.security.filter.AuthorizationTokenFilter;
import com.zeal.zealsay.security.handler.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
//import com.zeal.zealsay.security.filter.JwtAuthorizationTokenFilter;
/**
* spring security全局安全入口.
*
* @author zhanglei
* @date 2018/9/26 下午8:36
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {
@Autowired
RedisConnectionFactory redisConnectionFactory;
@Autowired
FilterIgnorePropertiesConfig filterIgnorePropertiesConfig;
@Autowired
PasswordEncoder passwordEncoder;
@Autowired
MyAuthenticationFailureHandler myAuthenticationFailureHandler;
@Autowired
MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
@Autowired
MyAccessDeniedHandler myAccessDeniedHandler;
@Autowired
MyAuthenticationEntryPoint myAuthenticationEntryPoint;
@Autowired
MyLogoutSuccessHandler myLogoutSuccessHandler;
@Autowired
UserDetailsService userDetailsService;
// @Autowired
// JwtAuthorizationTokenFilter jwtAuthorizationTokenFilter;
@Autowired
AuthorizationTokenFilter authorizationTokenFilter;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 加入自定义的安全认证
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry =
http
.csrf().disable()// 去掉 CSRF
.cors()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) // 使用 JWT,关闭token
.and()
.formLogin() //开启登录
.loginPage("/api/v1/authentication/require")
.loginProcessingUrl("/api/v1/authentication/login")
.successHandler(myAuthenticationSuccessHandler) // 登录成功
.failureHandler(myAuthenticationFailureHandler) // 登录失败
.permitAll()
.and()
.logout()
.logoutUrl("/api/v1/authentication/logout")
.logoutSuccessHandler(myLogoutSuccessHandler)
.and()
.authorizeRequests();
filterIgnorePropertiesConfig
.getUrls()
.forEach(url -> registry.antMatchers(url).permitAll());
registry
.anyRequest()
.authenticated()
// .access("@rbacauthorityservice.hasPermission(request,authentication)") // RBAC 动态 url 认证
.and()
.logout()
// .logoutSuccessHandler(logoutSuccessHandler)
.permitAll()
.and()
.rememberMe().rememberMeParameter("remember-me")
.userDetailsService(userDetailsService).tokenValiditySeconds(300)
.and()
.exceptionHandling().authenticationEntryPoint(myAuthenticationEntryPoint)
.and()
.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler) // 无权访问 JSON 格式的数据
.and()
.addFilterBefore(authorizationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
@Bean(name = BeanIds.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
filterIgnorePropertiesConfig
为配置在application.yml
中白名单的url