基础

  • spring security的底层就是一个过滤器链
  • ExceptionTranslationFilter是一个异常过滤器,用来处理认证授权过程中的异常
  • UseranmePasswordAuthenticationFilter,拦截/login的post请求,即认证时校验用户名、密码
  • spring boot整合spring security会自动化配置,即引入security依赖即可使用,不需要我们配置过滤器等
  • 认证,可理解为登录时的验证,当我们登录时就需要从数据库中查询用户名和密码,使用security只需实现UserDetailsService接口,在自定义的实现类中进行查询操作;之后返回一个User对象,这个对象是security为我们提供的,这个对象的属性包括查询到的用户名、密码、权限
  • 登录时输入的用户名和密码如何与数据库中的用户名密码比较,我们只需写一个类继承UsernamePasswordAuthenticationFilter,重写attemptAuthentication方法,在当前类中会接收登录时输入的用户名和密码,在attemptAuthentication方法中认证
点击查看源码
public class UsernamePasswordAuthenticationFilter extends
AbstractAuthenticationProcessingFilter {
// ~ Static fields/initializers
// ===================================================================================== public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password"; private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
private boolean postOnly = true; // ~ Constructors
// =================================================================================================== public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
} // ~ Methods
// ======================================================================================================== public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
} String username = obtainUsername(request);
String password = obtainPassword(request); if (username == null) {
username = "";
} if (password == null) {
password = "";
} username = username.trim(); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password); // Allow subclasses to set the "details" property
setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest);
}
}
  • PasswordEncoder接口用于数据加密,即密码加密
点击查看源码
package org.springframework.security.crypto.password;

public interface PasswordEncoder {
String encode(CharSequence var1); boolean matches(CharSequence var1, String var2); default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}

认证方式

  • 新建一个springboot项目,导入security依赖,任意访问一个控制器中的方法都需要认证,用户名为user,密码在控制台
  • 方式一:在配置文件yml中设置密码
  • 方式二:在配置类中设置(继承WebSecurityConfigurerAdapter,重写configure和password方法)
  • 方式三:在UserDetailsService实现类中查询数据库中的用户名和密码,将实现类注入配置类
点击查看实现类
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService { @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 设置权限
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_sale");
// 返回user对象,参数为模拟从数据库中查询到的用户名、密码
return new User("admin", new BCryptPasswordEncoder().encode("123456"),auths);
} }
点击查看配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired
private UserDetailsService userDetailsService; @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(password());
} @Bean
PasswordEncoder password() {
return new BCryptPasswordEncoder();
} }
  • 在配置类中指定自定义的登录页面,不需要认证就能访问的url
点击查看配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired
private UserDetailsService userDetailsService; //注入数据源
@Autowired
private DataSource dataSource; //配置对象
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
return jdbcTokenRepository;
} @Bean
PasswordEncoder password() {
return new BCryptPasswordEncoder();
} @Override
protected void configure(HttpSecurity http) throws Exception { // 退出登录
http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll(); // 没有访问权限时跳转的自定义页面
http.exceptionHandling().accessDeniedPage("/unauth.html"); // 自定义认证页面
http.formLogin().loginPage("/login.html")
// 登录的url
.loginProcessingUrl("/login")
// 登录成功后跳转的页面
.defaultSuccessUrl("/success.html")
// 登录失败的跳转页面
.failureUrl("/fail.html");
// 指定url不需要认证
http.authorizeRequests().antMatchers("/login.html").permitAll()
// 其他url需要认证
.anyRequest().authenticated()
.and().rememberMe().tokenRepository(persistentTokenRepository())
// 设置有效时长,单位秒
.tokenValiditySeconds(60)
.userDetailsService(userDetailsService);
// 关闭csrf防护
//http.csrf().disable();
} }
  • 认证业务逻辑:访问控制器方法 -> 实现类中查询 -> 认证通过 -> 执行控制器方法

项目案例

下载地址

授权

  • 在配置类中通过hasAuthority方法给指定url设置权限,在业务层实现类中给登录主体赋予权限
  .antMatchers("/test/index").hasAuthority("admins")
.antMatchers("/test/main").hasAuthority("user")
  • 在配置类通过hasAnyAuthority方法给指定url设置多个权限,访问时,只要具有其一权限即可访问
  .antMatchers("/test/index").hasAnyAuthority("admin,manager")

  // 业务层实体类赋予权限
AuthorityUtils.commaSeparatedStringToAuthorityList("admin,sale");
  • 在配置类中通过hasRole方法给指定url设置权限
  .antMatchers("/test/index").hasRole("sale")

  • 在配置类中通过hasAnyRole方法给指定url设置多个权限,访问时,只需具有其中一个权限即可访问
  .antMatchers("/test/index").hasAnyRole("sale,admin")

  // 在业务层实现类中赋予权限时需加上ROLE_前缀
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_admin,ROLE_sale");

注解方式授权

  // 1. 在启动类或配置类中开启注解功能
@EnableGlobalMethodSecurity(securedEnabled=true)
// 2. 在控制器的方法上添加角色权限,需加上ROLE_前缀;用户具有该角色权限时,可以访问该方法
@Secured({"ROLE_normal","ROLE_admin"})
// 3. 在业务层实现类中赋予登录用户角色权限
.antMatchers("/test/index").hasAnyRole("sale,admin") // 2. 在控制器上添加角色权限,可使用hasAuthority、hasRole、hasAnyRole等;在进入方法前验证是否具有角色权限
@PreAuthorize("hasAnyAuthority('admins,manager')")
// 3. 在业务层实现类中赋予登录用户角色权限 // 2. 在控制器上添加角色权限,可使用hasAuthority、hasRole、hasAnyRole等;在方法执行完验证是否具有角色权限;当我们访问这个方法,执行完之后没有权限时则跳转到403(没有权限)页面
@PostAuthorize("hasAnyAuthority('admins,manager')")
// 3. 在业务层实现类中赋予登录用户角色权限

认证业务逻辑

  • 自定义一个认证过滤器TokenLoginFilter继承UsernamePasswordAuthenticationFilter,登录时输入用户名和密码,进入认证过滤器TokenLoginFilter,获取登陆时的用户名和密码,进入UserDetailsServiceImpl实现类,该实现类实现了UserDetailsService接口,在UserDetailsServiceImpl中根据用户名去数据库查询用户信息,返回securityUser对象,认证成功后进入认证过滤器中的successfulAuthentication方法

源码分析

  • 自定义的认证过滤器TokenLoginFilter继承UsernamePasswordAuthenticationFilterUsernamePasswordAuthenticationFilter继承了AbstractAuthenticationProcessingFilter,在该类中的doFilter方法会判断该请求是否是post请求,不是则放行,是post则拦截认证
  • 之后在UsernamePasswordAuthenticationFilter类中的attemptAuthentication方法会获取表单提交的数据,然后进行认证(查数据库,比较),认证通过后,将用户数据封装到Authentication
点击查看源码
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
String username = this.obtainUsername(request);
username = username != null ? username : "";
username = username.trim();
String password = this.obtainPassword(request);
password = password != null ? password : "";
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
  • 认证成功后在AbstractAuthenticationProcessingFilter类中会将数据存入session
  • 认证失败时异常过滤器ExceptionTranslationFilter会抛出异常,执行unsuccessfulAuthentication方法;认证成功时则执行successfulAuthentication方法

attemptAuthentication方法源码分析

  • 判断是否是post提交,不是则抛出异常,是post则继续执行
  • 获取表单中提交的数据
  • new一个UsernamePasswordAuthenticationToken对象,将表单提交的数据构建进该对象,并标记为未认证状态
  UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);

  • 调用authenticate方法进行认证,该方法会调用UserDetailsService的实现类查数据库进行认证

UsernamePasswordAuthenticationToken对象构建源码分析

  • UsernamePasswordAuthenticationToken继承了一个抽象类AbstractAuthenticationToken
  • UsernamePasswordAuthenticationToken类中有两个方法,根据传入的参数调用指定的方法,调用UsernamePasswordAuthenticationToken方法表示标记为未认证状态,调用UsernamePasswordAuthenticationToken方法表示将对象标记未已认证状态
点击查看源码
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = 540L;
private final Object principal;
private Object credentials; public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super((Collection)null);
this.principal = principal;
this.credentials = credentials;
this.setAuthenticated(false);
} public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true);
} }
  • 抽象类AbstractAuthenticationToken则是实现了Authentication接口,该接口中包含一些用户信息
点击查看源码
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities(); Object getCredentials(); Object getDetails(); Object getPrincipal(); boolean isAuthenticated(); void setAuthenticated(boolean var1) throws IllegalArgumentException;
}

authenticate方法源码分析

  • 调用authenticate方法来进行认证,该方法是AuthenticationManager接口中的方法,该接口的实现类ProviderManager
  • 登录时表单提交的数据被封装进UsernamePasswordAuthenticationToken对象,然后传入authenticate方法;在ProviderManager实现类中authenticate方法会将UsernamePasswordAuthenticationToken对象中的信息迭代,之后判断该对象是否是UsernamePasswordAuthenticationToken类型,之后会调用authenticate方法将对象信息传入进行认证
  result = provider.authenticate(authentication);

  • 认证失败抛出异常,认证成功则将查询到的details复制到authentication对象中
try {
result = provider.authenticate(authentication);
if (result != null) {
this.copyDetails(authentication, result);
break;
}
} catch (InternalAuthenticationServiceException | AccountStatusException var14) {
this.prepareException(var14, authentication);
throw var14;
} catch (AuthenticationException var15) {
lastException = var15;
}

SpringSecurity入门的更多相关文章

  1. springSecurity入门小demo--配置文件xml的方式

    本例子只是一个最最最简单的入门demo,重点讲解xml的配置参数的意思和遇到的坑,主要的功能有: 自定义登录页面,错误页面 配置角色 csrf-403报错解决方法(加上一行代码配置就ok) 后台ifr ...

  2. SpringSecurity入门demo

    配置依赖: <properties> <spring.version>4.2.4.RELEASE</spring.version> </properties& ...

  3. SpringSecurity入门例子及遇到的问题解决

    最近学习<Spring 实战>学习到了SpringSecurity,觉得书本上的例子过于复杂,而且不喜欢它基于java配置,更喜欢用xml文件进行配置 于是在极客学院网上学习,感觉挺不错的 ...

  4. SpringBoot 安全管理(一)

    SpringBoot 安全管理(一) 一.springSecurity入门 添加依赖 <dependency> <groupId>org.springframework.boo ...

  5. SpringSecurity身份验证基础入门

    对于没有访问权限的用户需要转到登录表单页面.要实现访问控制的方法多种多样,可以通过Aop.拦截器实现,也可以通过框架实现(如:Apache Shiro.Spring Security). pom.xm ...

  6. SpringSecurity 3.2入门(7)自定义权限控制介绍

    总结Spring Security的使用方法有如下几种: 一种是全部利用配置文件,将用户.权限.资源(url)硬编码在xml文件中. 二种是用户和权限用数据库存储,而资源(url)和权限的对应关系硬编 ...

  7. SpringSecurity 3.2入门(2)环境搭建

    由于目前Spring官方只提供Meven的下载方式,为了能以最快的速度入门使用框架,这里提供百度网盘下载链接. 注:本入门教程默认已经配置成功SpringMVC框架. 1.web.xml配置 < ...

  8. SpringSecurity快速入门

    作者:SingleXu 链接:https://www.jianshu.com/p/8212a559d633 来源:简书 著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 简介 Sp ...

  9. 【Spring-Security】Re01 入门上手

    一.所需的组件 SpringBoot项目需要的POM依赖: <dependency> <groupId>org.springframework.boot</groupId ...

随机推荐

  1. JavaScript实现拖放效果

    JavaScript实现拖放效果 笔者实现该效果也是套用别人的轮子的.传送门 然后厚颜无耻的贴别人的readme~,笔者为了方便查阅就直接贴了,有不想移步的可以看这篇.不过还是最好请到原作者的GitH ...

  2. CF896D Nephren Runs a Cinema

    CF896D Nephren Runs a Cinema 题意 售票员最开始没有纸币,每次来一个顾客可以给她一张.拿走她一张或不操作.求出不出现中途没钱给的情况 \(n\) 名顾客后剩余钱数在 \(l ...

  3. 1.4matlab矩阵的表示

    1.4matlab矩阵的表示 矩阵的建立 利用直接输入法建立矩阵:将矩阵的元素用中括号括起来,按矩阵的顺序输入各元素,同一行的各元素之间用逗号或空格分隔,不同行的元素之间用分号分隔. 利用已建立好的矩 ...

  4. 后台程序编译过程报错PCC-F-02104, Unable to connect to Oracle

    偶然重新编译了一下后台程序,发现编译过程报错无法连接数据库.但通过sqlplus登录数据库是正常的.后台程序改动中也做了详细的分析,没有改动相关数据库的参数和配置. 最后通过浏览器查看了很多相关问题的 ...

  5. 未知高度-纯css实现水平垂直居中

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  6. Nginx 文件名逻辑漏洞(CVE-2013-4547)

    影响版本 Nginx 0.8.41 ~ 1.4.3 / 1.5.0 ~ 1.5.7 漏洞成因 这个漏洞其实和代码执行没有太大关系,其主要原因是错误地解析了请求的URI,错误地获取到用户请求的文件名,导 ...

  7. BUUCTF-[网鼎杯 2018]Fakebook(SSRF+联合注入绕Waf)

    记一道SSRF配合SQL注入的题. 喜欢在做题之前扫一下网站的目录,扫到了robots.txt文件可以访问,拿到user.php.bak的源码.同时还有flag.php. <?php class ...

  8. Bugku-misc 1-8题总结

    1.签到题 略过 2.这是一张单纯的图片 拉入winhex,在最后面有一段Uniocde编码,解码得到flag. 3.隐写 题目是隐写,binwalk打开分析 得到两个Zlib(提供数据压缩用的函式库 ...

  9. RHCSA_DAY11

    删除逻辑卷 逻辑卷的删除不允许联机操作,需要先卸载,在执行删除 在执行删除操作时,首先删除LV逻辑卷,在删除VG卷组,最后删除PV物理卷 删除命令:lvremove #删除逻辑卷错误示范 [root@ ...

  10. 深入理解jvm-2Edition-类文件结构

    概述: 规范而独立的类文件结构以及只与类文件关联的虚拟机为Java实现了平台无关性,甚至还带来了一些语言无关性. 只要将源代码编译为Class文件规定的格式,JVM就可以执行. JVM的指令描述能力比 ...