spring sercurity 基础
环境配置:
win10
idea 2019.1
jdk1.8
spring cloud Finchley.RELEASE
spring-boot 2.0.2.RELEASE
git && gitee.com
基本原理
一系列的过滤器组成的链路.如下图:
FilterSercurityInterceptor就是spring security处理鉴权的入口,在访问REST api 前都会经过这个过滤器,如果通过了鉴权才会跳转到对应api的入口
下面来分析在默认开启spring security的情况下各种场景的一些执行路径,通过流程图展示出来;
对应操作: 打断点在FilterSecurityInterceptor.doFilter()
上,即可追踪其执行路径
未登录,请求REST api
已登录,请求REST api,权限不够
已登录,请求REST api,权限足够
自定义用户认证逻辑
自定义认证配置信息:
需要继承WebSecurityConfigurerAdapter
,并重写其configure()
方法.
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 说明: 重写security的配置方法
* @author suwenguang
* @date 2019/6/7
* @return void <- 返回类型
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置
http.formLogin()
.and()
.authorizeRequests()
.anyRequest()
.authenticated();
}
}
处理用户信息获取逻辑
实现UserDetailsService
接口
@Component
@Slf4j
public class UserCertificationService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("用户名:{}", username);
log.info("密码:{}",passwordEncoder.encode("123456"));
//根据username到数据库获取用户信息
return new User(username, passwordEncoder.encode("123456")
,true,true,true,true,
AuthorityUtils.commaSeparatedStringToAuthorityList("admin,user"));
}
}
处理用户校验逻辑
利用UserDetails
UserDetails
是个接口,可以实现等方式创建自己系统的子类,以达到扩展的效果.当然也可以使用自带的.
org.springframework.security.core.userdetails.User
UserDetails内部提供如下的四种状态
- 账号是否已过期
isAccountNonExpired
- 账号是否锁定
isAccountNonLocked
- 密码是否已过期
isCredentialsNonExpired
- 账号是否启用
isCredentialsNonExpired
处理密码加密解密
PasswordEncoder
配置一个PasswordEncoder
新建PasswordEncoderConfig.java
@Configuration
public class PasswordEncoderConfig {
/**
* 说明: 注入一个明文密码的加解密
* @author suwenguang
* @date 2019/6/7
* @return org.springframework.security.crypto.password.PasswordEncoder <- 返回类型
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
个性化用户认证
form-login属性详解
form-login是spring security命名空间配置登录相关信息的标签,它包含如下属性:
login-page
自定义登录页url,默认为/login
login-processing-url
登录请求拦截的url,也就是form表单提交时指定的actiondefault-target-url
默认登录成功后跳转的urlalways-use-default-target
是否总是使用默认的登录成功后跳转url
authentication-failure-url
登录失败后跳转的urlusername-parameter
用户名的请求字段 默认为userName
password-parameter
密码的请求字段 默认为password
authentication-success-handler-ref
指向一个AuthenticationSuccessHandler
用于处理认证成功的请求,不能和default-target-url
还有always-use-default-target
同时使用authentication-success-forward-url
用于authentication-failure-handler-ref
authentication-failure-handler-ref
指向一个AuthenticationFailureHandler
用于处理失败的认证请求authentication-failure-forward-url
用于authentication-failure-handler-ref
authentication-details-source-ref
指向一个AuthenticationDetailsSource
,在认证过滤器中使用
自定义登录界面
- 设置 上述属性
login-page
在请求需要登录认证的api,发现用户没有经过登录认证,spring security 会自动跳转到配置的login-page
的路由(方法,url,controller...) - 如果是rest api 服务(前后端分离),不需要返回html页面,可以在指定的
login-page
直接返回json格式的响应 - 如果是需要返回html页面,也可以在
login-page
的controller返回页面 - 在
login-page
的controller对请求作出判断,选择不同的返回方式即可
自定义登录成功处理
前后端分离的情况下,登录请求,客户端需要的是一个rest的返回结果,而不是跳转url,这就需要我们去做一些自定义的配置
- 创建一个类实现
AuthenticationSuccessHandler
接口,实现onAuthenticationSuccess()
方法,把Authentication
信息包装成json返回.
新建SystemAuthenticationSuccessHandler.java
@Component
@Slf4j
public class SystemAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
/**
* 说明:登录成功后,执行的方法
* @author suwenguang
* @date 2019/6/8
* @return void <- 返回类型
*/
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
log.info("登录成功:{}", JSON.toJSONString(authentication));
response.setContentType("application/json;charset=UTF-8");
PrintWriter writer = response.getWriter();
Response res = ResponseBuilder.build(ResponseEnums.SIMPLE_SUCCESS, authentication);
writer.write(JSON.toJSONString(res));
}
}
- 在spring security config类里面配置上
successHandler()
,把刚刚的类作为参数传入即可
修改SecurityConfig.java
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private SystemAuthenticationSuccessHandler systemAuthenticationSuccessHandler;
/**
* 说明: 重写security的配置方法
* @author suwenguang
* @date 2019/6/7
* @return void <- 返回类型
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置
http.formLogin()
//自定义登录方法
.loginPage("/login.html")
.loginProcessingUrl("/login")
//自定义登录认证成功处理器
.successHandler(systemAuthenticationSuccessHandler)
.and()
.authorizeRequests()
//配置不需要认证的路由
.antMatchers("/login.html","**static**").permitAll()
//所有路由都要认证
.anyRequest().authenticated()
//禁用跨站攻击防护机制
.and().csrf().disable()
;
}
}
自定义登录失败处理
道理同上,但是这次需要实现的接口变成了AuthenticationFailureHandler
,其他步骤几乎同上
下面贴上处理的代码
新建SystemAuthenticationFailHandler.java
@Component
@Slf4j
public class SystemAuthenticationFailHandler implements AuthenticationFailureHandler {
/**
* 说明: 认证失败后,执行的方法
* @author suwenguang
* @date 2019/6/8
* @return void <- 返回类型
*/
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
log.info("登录失败:{}", exception.getMessage());
response.setContentType("application/json;charset=UTF-8");
PrintWriter writer = response.getWriter();
Response res = ResponseBuilder.build(ResponseEnums.LOGIN_FAIL, exception.getMessage());
writer.write(JSON.toJSONString(res));
}
}
修改SecurityConfig.java
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 登录成功的处理器
**/
@Autowired
private SystemAuthenticationSuccessHandler systemAuthenticationSuccessHandler;
/**
* 登录失败的处理器
**/
@Autowired
private SystemAuthenticationFailHandler systemAuthenticationFailHandler;
/**
* 说明: 重写security的配置方法
* @author suwenguang
* @date 2019/6/7
* @return void <- 返回类型
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置
http.formLogin()
//自定义登录方法
.loginPage("/login.html")
.loginProcessingUrl("/login")
//自定义登录认证成功处理器
.successHandler(systemAuthenticationSuccessHandler)
//自定义登录失败处理器
.failureHandler(systemAuthenticationFailHandler)
.and()
.authorizeRequests()
//配置不需要认证的路由
.antMatchers("/login.html","**static**").permitAll()
//所有路由都要认证
.anyRequest().authenticated()
//禁用跨站攻击防护机制
.and().csrf().disable()
;
}
}
2019-06-08 20:54:47 星期六
来源:oschina
链接:https://my.oschina.net/u/3641461/blog/3059479