想写这篇博客很久了,但是一直就是自己心里明镜,但是要很好的总结出来,还是会有无从下手的感觉。但我还是开始写吧。一直拖 早着才能写完。
github的项目源码,建议 导入项目后,运行 然后看着博客学习。
document\urp.sql 数据库 sql
博客只是能把你领进门,有很多特性我没有讲,感兴趣的 可以看 官方 文档
spring sercurity官方文档
如果只是满足于怎么样spring sercurity博客就可以,如果想各种定制那么还是官方文档和源码吧。
谈一下 个人对spring security的理解,权限控制,这是最重要的,并且可以高度定制化,这一点学完以后大家就明白了。其次就是 spring sercurity 用了Filter和AOP。所以,大家即使不用spring sercurity 只用最简单的Filter 也是可以自己整一个权限框架出来的,至于扩展性,安全性 ,健壮性,就看你的代码水平了。
我用的是spring boot项目,所以 我说一下 在spring boot项目中 ,如何引入spring sercurity。
在pom.xml文件里面
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-bomartifactId>
<version>5.1.6.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
权限控制最主要的两大特征:
在学习之前,我们需要记住。spring sercurity是基于Filter的。牢记这一点对你的学习很有帮助。
认证流程的主要作用:就是辨别你是谁,你的身份,你的权限等
认证流程图
Authentication:认证对象,这个会贯串整个spring sercurity流程。携带权限信息列表,密码,用户身份信息。这个会在整个流程中流动。要注意是否完全填充
Http Request:这个就是request请求,没啥好说的。
AuthenticationFilter: 这是spring sercurity的基础架构可以有很多个,用于提取用户名,密码,管理session。管理匿名访问等一系列的事。spring sercurity通过Filter Chain来管理调用这些AuthenticationFilter:这些Filter顺序极其重要,不管你如果自定义Filter,都需要遵守下面的顺序。
到这里,如果 你使用上面的Filter自定义子类来替换,并且可以生成Authentication认证对象。那么认证流程就结束了。
note SecurityContextPersistenceFilter, ExceptionTranslationFilter 和FilterSecurityInterceptor 这些由 元素创建的,不能替换掉.
现在说一下,spring sercurity自带的默认实现类,也就是上面图上面画的一些元素。
AuthenticationManager:这是一个接口,有一个authenticate方法,最重要的实现类是ProviderManager。这个会在上面的UsernamePasswordAuthenticationFilter里面持有。Filter调用AuthenticationManager的authenticate方法,来进行身份认证。 note,但是AuthenticationManager并不执行具体的认证过称,而是由它持有的AuthenticationProvider集合来处理。只要有一个AuthenticationProvider认证通过,那么就不会再调用下面的AuthenticationProvider。
AuthenticationProvider:具体的执行认证过称的接口,有一个上面AuthenticationManager相同的方法。常用的实现类有DaoAuthenticationProvider,内部持有一个下面的UserDetailsService 用于提供UserDetails.然后 也是在这里进行additionalAuthenticationChecks()密码的比对。
UserDetailsService:加载用户的数据,例如 用户名,密码 权限等,被AuthenticationProvider持有。会返回一个UserDetails。
UserDetails: 提供核心用户信息。 Spring Security不直接UserDetails将用于安全目的。它们只是存储用户信息,**这些信息稍后被封装到Authentication对象中。**这允许将与安全无关的用户信息(如电子邮件地址、电话号码等)存储在一个方便的位置。
通常 自己实现。也有系统默认的实现User。
1Http Request 请求进入,AuthenticationFilter按照它们的顺序进行拦截,其中有一个UsernamePasswordAuthenticationFilter会将1Http Request 中的userName和password 提取出来,生成一个没有完全填充的Authentication认证对象。这个认证对象随即就会被传入UsernamePasswordAuthenticationFilter的内部变量AuthenticationManager的authenticate方法,AuthenticationManager会调用自己内部的AuthenticationProvider集合,对上面生成的Authentication对象 进行认证。AuthenticationProvider会调用UserDetailsService,UserDetailsService则根据传入的userName 来取出 我们在数据库中的userName和password和权限,并把这些userName,password 和权限信息封装到生成的UserDetails里面。并且进行密码的校验。密码正确的就会生成一个完全填充的Authentication认证对象,并把这个完全填充的Authentication对象放到SecurityContextHolder的SecurityContext里面。认证流程就可以结束了。下面是鉴权流程 才会开始、
正如现在网站开发,分为 前后端分离,以及freemarker形式的半分离,以及JSP这种完全不分离的形式。spring sercurity可以完美支持这三种形式。
这里简单说一下在spring security中的区别:
前后端分离的一般用到token,其他的就是普通的表单登录基于session的。
我下面也分为两种分别进行讲解,虽然 其实如果你熟悉以后,会发现 差别吧 并不是很大,但是为了让新手更快速的学习,所以 需要纯粹一点。
在引入spring sercurity以后,我们需要创建一个WebSecurityConfigurerAdapter的子类。FormSecurityConfig 并添加@Configuration和@EnableWebSecurity注解
/**
* 配置form 登录的
*
* @Author: plani
* 创建时间: 2019/8/16 17:50
*/
@Configuration
@EnableWebSecurity
//启用 方法鉴权 全局 只能有一个类 有这个注解。
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
@Order(3)//通过这个数字 来决定两个config那个生效
public class FormSecurityConfig extends WebSecurityConfigurerAdapter {
private Logger logger = LoggerFactory.getLogger(FormSecurityConfig.class);
@Autowired
private PermissonService permissonService;
@Autowired
private RestfulAccessDeniedHandler restfulAccessDeniedHandler;
@Autowired
private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
@Autowired
private MyAuthticationProvider myAuthticationProvider;
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()//关闭csrf
.formLogin()//设置表单登录的一些细节,就是对 form表达提交 做了一些封装。完全可以自己直接请求接口
.loginPage("/login")//用户没有登录的话,登录页面,这个需要你在controller方法里面定义这个,然后 转到
//处理的url,这个会把filter的拦截url 改成/myaction,没有什么影响,默认是 login
//如果 这里 你自定义了 url 那么,form 表单的action 就必须是 /myaction
.loginProcessingUrl("/myaction")
.usernameParameter("userName")//设定参数名字
.passwordParameter("password")//设定参数名字
.failureUrl("/login?error")//这个是验证密码错误,以后要跳转的页面,默认就是 /login?error
.successForwardUrl("/index/success")//这是一个post转发请求,注意post
.permitAll()//放开限制,允许任何人访问
//下面的会覆盖 上面的 successForwardUrl 和 failureUrl ,使他们不起作用。
/* .successHandler((request, response, authentication) -> {
//成功认证 以后的 处理。 可以放一些业务需要的逻辑
logger.info("认证成功");
})
.failureHandler((request, response, exception) -> {
//认证失败以后的处理
logger.info("认证失败");
})*/
.and()
.logout().logoutUrl("/logout")//退出 url ,默认就是 /logout
.and()
.authorizeRequests()//对requset 开启访问控制
// index/asdf 可以被任何人访问,访问这个url 如果没有登录的话,也不会跳到登录页面
.antMatchers("/index/asdf").permitAll()
//这个匹配器将使用与Spring MVC用于匹配的相同的规则。例如,通常路径“/path”的映射会与“/path”、“/path/”、“/path.html”匹配
.mvcMatchers("mvc").authenticated()
//其他的任何 url都开启 认证。 要注意静态资源
.anyRequest().authenticated()
.and()
.httpBasic();//开启httpBasic
//下面这个配置 会把上面的 loginPage覆盖掉。 form登录时候 就不要用这个了
/* //配置 全局异常 处理方案
httpSecurity.exceptionHandling()
//没有权限访问 时候 返回信息
.accessDeniedHandler(restfulAccessDeniedHandler)
//当未登录或者token失效访问接口时,自定义的返回结果
.authenticationEntryPoint(restAuthenticationEntryPoint);*/
//使用自己定义的 authenticationProvider
// httpSecurity.authenticationProvider(myAuthticationProvider);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth // 设置UserDetailsService
.userDetailsService(userDetailsService())
// 使用BCrypt进行密码的hash
.passwordEncoder(passwordEncoder());
// .and()
// .authenticationProvider(myAuthticationProvider);
}
// 装载BCrypt密码编码器
@Bean
public PasswordEncoder passwordEncoder() {//密码加密
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService userDetailsService() {
//获取登录用户信息
return new UserDetailsService() {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
logger.info("username " + username);
User user = permissonService.getUserByUserName(username);
if (user != null) {
return new SecurityUser(user, permissonService);
}
throw new UsernameNotFoundException("没有用户");
}
};
}
}
上面这个类中,我们 在AuthenticationManagerBuilder进行设置,配置了 自己的 userDetailsService,使用默认的PasswordEncoder。
在HttpSecurity 哪里,关闭了 开启了 form登录,这里指的注意的就是 所有的url, 前面加 " / " 比较好。还有就是 authorizeRequests后面的表达式 是按照顺序来执行的!前面的优先级最高
要注意.successForwardUrl("/index/success") 登录成功以后 ,转发的是post请求。
SecurityUser 这个类不是必须的,
/**
* @Author: plani
* 创建时间: 2019/8/16 17:54
*/
public class SecurityUser implements UserDetails {
private User user;
private PermissonService permissonService;
public SecurityUser(User user, PermissonService permissonService) {
this.user = user;
this.permissonService = permissonService;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<Role> roles = permissonService.rolesByUser(user);
List<GrantedAuthority> authorities = null;
if (roles!=null&&!roles.isEmpty()){
authorities= roles.stream().map(new Function<Role, GrantedAuthority>() {
@Override
public GrantedAuthority apply(Role role) {
//用注解时候 hasRole 判断 角色前面 加 "ROLE_"
// return new SimpleGrantedAuthority("ROLE_"+role.getRoleName());
//hasAuthority 的时候 就不需要加任何前缀
return new SimpleGrantedAuthority(role.getRoleName());
}
}).collect(Collectors.toList());
}
return authorities;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUserName();
}
//账户是否未过期,过期无法验证
@Override
public boolean isAccountNonExpired() {
return true;
}
//指定用户是否解锁,锁定的用户无法进行身份验证
@Override
public boolean isAccountNonLocked() {
return true;
}
//指示是否已过期的用户的凭据(密码),过期的凭据防止认证
@Override
public boolean isCredentialsNonExpired() {
return true;
}
//是否可用 ,禁用的用户不能身份验证
@Override
public boolean isEnabled() {
return true;
}
}
SecurityUser 并不是必须的 ,框架有自己的实现类 User。 只是为了好理解,简单的权限 可以这么使用。复杂的权限,这个就不太灵活了。
/**
* @Author: plani
* 创建时间: 2019/8/16 17:54
*/
public class SecurityUser implements UserDetails {
private User user;
private PermissonService permissonService;
public SecurityUser(User user, PermissonService permissonService) {
this.user = user;
this.permissonService = permissonService;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<Role> roles = permissonService.rolesByUser(user);
List<GrantedAuthority> authorities = null;
if (roles!=null&&!roles.isEmpty()){
authorities= roles.stream().map(new Function<Role, GrantedAuthority>() {
@Override
public GrantedAuthority apply(Role role) {
//用注解时候 hasRole 判断 角色前面 加 "ROLE_"
// return new SimpleGrantedAuthority("ROLE_"+role.getRoleName());
//hasAuthority 的时候 就不需要加任何前缀
return new SimpleGrantedAuthority(role.getRoleName());
}
}).collect(Collectors.toList());
}
return authorities;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUserName();
}
//账户是否未过期,过期无法验证
@Override
public boolean isAccountNonExpired() {
return true;
}
//指定用户是否解锁,锁定的用户无法进行身份验证
@Override
public boolean isAccountNonLocked() {
return true;
}
//指示是否已过期的用户的凭据(密码),过期的凭据防止认证
@Override
public boolean isCredentialsNonExpired() {
return true;
}
//是否可用 ,禁用的用户不能身份验证
@Override
public boolean isEnabled() {
return true;
}
}
controller
/**
* @Author: plani
* 创建时间: 2019/8/19 10:30
*/
@Controller
@RequestMapping("/index/")
public class IndexControllerr {
private Logger logger = LoggerFactory.getLogger(IndexControllerr.class);
@RequestMapping("root")
//要用 root 权限 才可以访问这个接口
@PreAuthorize("hasAuthority('admin')")
public String root(Model model) {
logger.info("我进来了");
//可以通过 SecurityContextHolder.getContext().getAuthentication() 来得到当前的认证对象
//Authentication 有很多信息,你可以自己实现一个类,任何在filter中调用SecurityContextHolder.getContext().setAuthentication(); 存储你的认证对象
//这里得到的有可能是 你自定义的,也有可能是 匿名的认证对象。
model.addAttribute("user", SecurityContextHolder.getContext().getAuthentication().getName());
return "index";
}
//不需要 任何权限都可以访问,但这个不代表 任何人都可以访问这个接口,这个需要看你的设置。
@RequestMapping("norole")
public String noRole(Model model) {
return "norole";
}
@RequestMapping("success")
public String success() {
return "success";
}
//在配置文件里面 放开这个接口 ,也没有配置鉴权相关的注解,所以 访问这个接口没有任何限制
@RequestMapping("asdf")
public String asd() {
System.out.println("ssssssssssssssssssssssss");
return "asdf";
}
}
上面这个里面 主要定义了, 一个 “/index/root” 路径,需要有"admin"权限。 “/index/norole” 不需要 权限,只需要 登录就行。“asdf” 这个路径 不需要登录 你就可以访问他。
这三个路径要和上面的配置文件 一起看有效,在上面 .antMatchers("/index/asdf").permitAll() 放开了 “/index/asdf” 这个路径访问 不受控制。 其他的路径,在上面的配置文件中,配置了 都需要 登录才行,
.anyRequest().authenticated() 。这段代码的意思就是 其他的任何路径,都需要认证过也就是登录状态。
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<meta charset="UTF-8">
<title>Login pagetitle>
head>
<body>
<h1>Login pageh1>
<div style="width:100%;text-align:center">
<div th:if="${param.error}">
Invalid username and password.
div>
<div th:if="${param.logout}">
You have been logged out.
div>
<form th:action="@{/myaction}" method="post" style="padding: auto">
<label for="username">Usernamelabel>:
<input type="text" id="userName" name="userName" autofocus="autofocus" style="margin-top: 10%"/> <br/>
<label for="password">Passwordlabel>:
<input type="password" id="password" name="password"/> <br/>
<input type="submit" value="Login"/>
form>
div>
body>
html>
这个就是自定义的登录页面 很简陋, 注意点 就是 action 要和 你上面.loginProcessingUrl("/myaction") 要保持一致。
先访问http://localhost:8888/index/asdf ,由于这个接口是放开的,所以 不登录就可以访问。
启动上面的项目,访问http://localhost:8888/index/root。因为这个路径 需要登录,所以 跳到了/login 页面,又因为我在ViewControllerRegistry 哪里 添加了 registry.addViewController("/login").setViewName(“login”); 把"/login"指向 login.html。
输入 用户名,密码。会发现 跳到 我们自定义的 登录成功界面。
在浏览器的控制台 也多了一个请求,我们查看一些
我们可以看到 提交表单后,会提交userName和password参数 到我们自定义的 路径去。并且 请求 还携带了 Cookies。 登录成功以后 返回我们自定义的成功页面。并且 返回一个Cookies。这就是为什么 form是基于session的。
再请求 需要权限的路径,
请求的时候 携带了 登录成功以后 返回的 cookis值。这个就相当于 是token,只不过 在form表单里面是放在cookies里面。
基于token登录的 spring sercurity 框架 其实已经有了 这方法的实现 ,和OAuth2 一起的,公司目前的项目 就是用的这个OAuth。这个博客 不写OAuth2.对于新手来说,一开始的时候 要,贪多嚼不烂。大道至简,才是永恒的真理
创建一个配置文件 TokenSecurityConfig .
/**
* 配置token登录的
*
* @Author: plani
* 创建时间: 2019/8/16 17:50
*/
@Configuration
@EnableWebSecurity
//启用 方法鉴权
//下面这个注解 ,项目只能有一个类可以使用,要注意
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
@Order(3)
public class TokenSecurityConfig extends WebSecurityConfigurerAdapter {
private Logger logger = LoggerFactory.getLogger(FormSecurityConfig.class);
@Autowired
private PermissonService permissonService;
@Autowired
private RestfulAccessDeniedHandler restfulAccessDeniedHandler;
@Autowired
private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
@Autowired
private MyAuthticationProvider myAuthticationProvider;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable()//跨域攻击去除
.sessionManagement()// 基于token,所以不需要session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()//返回SecurityBuilder
.authorizeRequests()//配置url
//这里应该考虑 放开 你的注册接口, 登录接口默认是 /login
//这里的antMatchers 是在 Filter Chain 最后的Filter起效果。所以 不用放开 /login
.antMatchers("/register")
.permitAll()////允许所有人访问
.anyRequest()//所有请求
.authenticated();//授权的用户
httpSecurity
//Filter Chain 里面的UsernamePasswordAuthenticationFilter类的位置 添加 我们自定义的Filter
//在指定Filter类的位置添加筛选器,要注意 位置,这个不要求是Sercurity的 Filter 实例
.addFilterAt(jwtLoginFilter(), UsernamePasswordAuthenticationFilter.class)
//在 UsernamePasswordAuthenticationFilter 的位置前面加入 Filter
// .addFilterBefore(jwtLoginFilter(), UsernamePasswordAuthenticationFilter.class)
//添加一个Filter,该Filter必须是安全框架中提供的Filter的实例或扩展其中一个Filter。该方法确保自动处理Filter的排序。
// .addFilter(jwtLoginFilter())
.addFilterAt(jwtAuthenticationFilter(), BasicAuthenticationFilter.class)
//这个 MyFilter
.addFilterAt(new MyFilter(), LogoutFilter.class);
//配置 全局异常 处理方案
httpSecurity.exceptionHandling()
//没有权限访问 时候 返回信息
.accessDeniedHandler(restfulAccessDeniedHandler)
//当未登录或者token失效访问接口时,自定义的返回结果
.authenticationEntryPoint(restAuthenticationEntryPoint);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth // 设置UserDetailsService
.userDetailsService(userDetailsService)
// 使用BCrypt进行密码的hash
.passwordEncoder(passwordEncoder);
// .and()
// .authenticationProvider(myAuthticationProvider);
}
@Bean
//这种用 @Bean的 并不是 唯一的,如果 你想 ,你可以直接在上面使用的地方 new一个,不过 类里面的一些其他组件 就没法注入了
public JWTAuthenticationFilter jwtAuthenticationFilter() throws Exception {
return new JWTAuthenticationFilter(authenticationManager());
}
@Bean
public JWTLoginFilter jwtLoginFilter() throws Exception {
JWTLoginFilter jwtLoginFilter = new JWTLoginFilter();
jwtLoginFilter.setAuthenticationManager(authenticationManagerBean());
return jwtLoginFilter;
}
}
校验密码成功后,将生成的token 放入header中返回
/**
* @Author: plani
* 创建时间: 2019/8/19 9:49
*/
//UsernamePasswordAuthenticationFilter 默认拦截 /login 路径 获取参数
public class JWTLoginFilter extends UsernamePasswordAuthenticationFilter {
@Autowired
private JwtTokenUtil jwtTokenUtil;
//在这里进行 进行身份认证。这个方法 会被父类调用
//认证失败的 直接抛出异常就行 异常必须是AuthenticationException的本身或者 子类
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
UsernamePasswordAuthenticationToken authRequest = null;
//处理 json数据
if (MediaType.APPLICATION_JSON_VALUE.equals(request.getContentType()) || MediaType.APPLICATION_JSON_UTF8_VALUE.equals(request.getContentType())) {
ObjectMapper objectMapper = new ObjectMapper();
try {
//这里使用自己熟悉的json框架都行
JsonNode jsonNode = objectMapper.readTree(request.getInputStream());
String username = jsonNode.get("username").asText("");
String password = jsonNode.get("password").asText("");
username = username.trim();
//用于简单表示用户名和密码的身份验证}实现。
authRequest = new UsernamePasswordAuthenticationToken(
username, password);
} catch (IOException e) {
//抛出异常 ,必须是 AuthenticationException的子类
throw new InternalAuthenticationServiceException(e.getMessage());
}
//setDetails(request, authRequest) 是将当前的请求信息设置到 UsernamePasswordAuthenticationToken 中。
setDetails(request, authRequest);
//调用 AuthenticationManager 进行 认证。这个就是我上面说的流程了
//你也可以在这里 就进行 密码的验证,一切看自己 可定制度很高
return this.getAuthenticationManager().authenticate(authRequest);
} else {
//其他的走,父类默认的方法
return super.attemptAuthentication(request, response);
}
}
//只要上面的 attemptAuthentication没有抛出异常 ,就一定会走这里
// 用户成功登录后,这个方法会被调用,我们在这个方法里生成token
@Override
protected void successfulAuthentication(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain,
Authentication auth) throws IOException, ServletException {
// 把完全填充的Authentication 设置 就是下面的这一行代码
SecurityContextHolder.getContext().setAuthentication(auth);
//把token返回 放在header中
String token = jwtTokenUtil.generateToken(((SecurityUser) auth.getPrincipal()).getUsername());
res.addHeader("Authorization", "Bearer" + token);
res.getOutputStream().write("success token".getBytes());
}
//这个是 登录失败,会被调用的 ,也就是上面attemptAuthentication 抛出异常 就会走这个方法
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
//这里可以自己 通过 response 写返回的信息,
response.getOutputStream().write("error".getBytes());
//也可以调用父类的 方法,走spring sercurity 定义好的流程 返回 ,配置文件里面定义的那些 failureHandler
//默认实现的failureHandler 最后会指向/ 路径 ,这个要注意。
// super.unsuccessfulAuthentication(request, response, failed);
}
}
丛header中 提取 token 进行 校验,通过了 就生成 完整的认证对象。放入sercurity context中。
package com.example.sercurity.config.sercurity;
import com.example.sercurity.utils.JwtTokenUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @Author: plani
* 创建时间: 2019/8/19 17:08
*/
//并不一定 非要用这个BasicAuthenticationFilter 作为父类,可以根据Filter的顺序 自己选择
public class JWTAuthenticationFilter extends BasicAuthenticationFilter {
private Logger logger = LoggerFactory.getLogger(JWTAuthenticationFilter.class);
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Value("${jwt.tokenHeader}")
private String tokenHeader;
@Value("${jwt.tokenHead}")
private String tokenHead;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
//获取 header
String authHeader = request.getHeader(this.tokenHeader);
if (authHeader != null && authHeader.startsWith(this.tokenHead)) {
//获取 token
String authToken = authHeader.substring(this.tokenHead.length());// The part after "Bearer "
//提取出 名字
String username = jwtTokenUtil.getUserNameFromToken(authToken);
logger.info("token 提取的username >>> ", username + " >>>>");
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(authToken, userDetails.getUsername())) {
//三个参数的构造方法 就是setAuthenticated 认证过了 ,可以看源码
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
logger.info("authenticated user:{}", username);
//设置到 SecurityContext里面
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
//否则的 直接走流程 调用下一个Filter
chain.doFilter(request, response);
}
}
运行项目 ,打开postman 输入url 和参数
可以看见 header中已经 有了token,下次请求时候 把这个token放入header中就行。
控制台还有 这么一个 日志。这个日志 就是配置文件 里面 MyFilter类 打印的。
接下来 请求其他的接口
说到这里 认证这一部分 就结束 了。有的细节 我就省略了,因为 spring sercurity的知识点很杂,不可能都说完。我的代码里面 每个注释 都很重要 ,不重要的 我就不会写出来了。最重要的时候,上面的流程 一定要看懂,事半功倍
也是我写的博客
spring sercurity之鉴权