文章目录
什么是Spring Security
是个安全框架,就是用来验证身份和权限的那种。这个框架使用十分广泛,但是并不是很好理解,我在公司的新项目中就需要使用这个框架进行用户身份的验证和识别,并且这个需求要与JWT进行整合使用。
什么是JWT
JWT是Json Web Token的简称,我比较喜欢叫他用户令牌,用户在访问一些需要授权的地址的时候就需要在http的header上面携带Token,这样服务器就能够认识这个用户,并且了解他所拥有的权限。
Token的来源就是用户的登录,用户在登录的时候,服务器在验证了用户名和密码之后就会生成一个Token。
JWT包含这些部分:头部,明文负载,加密签名。这三部分被编码为Base64的形式,之间使用点.
连起来,是长长的一串,通过Base64解码可以直接得到前两部分的具体内容,一般会包括一些类似于签发人,加密算法等相关内容,服务器可以通过这些内容验证一个用户的身份。
使用JJWT库可以很方便的生成一个JWT,这个后续在详细说明。
Spring Security工作流程
在用了一下之后,粗略的整理了一个思维导图,是这样的:
首先,用户发送了一个请求,请求被拦截器拦截,如果是登陆地址的拦截器,他就会将用户名和密码封装到一个对象,然后转交AuthenticationManager通过authenticate
方法认证,但是Token可能有很多种,AuthenticationManager并不了解如何认证这些对象,所以他会寻找可以处理此对象的Provider
,Provider同样实现了authentic方法,这个方法就是用来验证Token身份和权限的方法,如果验证成功,那么他将会返回一个已经验证的Token。
基础使用方式
实现Token
Token就是在SpringSecurity中用来验证的东西,一般是Authentication
接口的实现类,这个接口有这些方法:
- Collection<? extends GrantedAuthority> getAuthorities();
角色,就是用户在系统中的身份,例如管理员,工作人员,游客等,这些一般就决定了用户会有哪些权限 - Object getCredentials();
一个可以验证身份的东西,可以是密码,也可以是别的。 - Object getDetails();
一些附加数据,常会用来放置附加了账号权限信息的东西 - Object getPrincipal();
用户的简要资料 - boolean isAuthenticated();
是否认证通过 - void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
设置Token的状态,一般是创建的时候为false,认证成功后为true
可以自己实现一个此接口的类,也可以继承Spring Security
中给出的其他Token,总之,按照实际情况的需要来编写它。这次的工程中,我在这个工程中的做法是继承了AbstractAuthenticationToken
这个类,然后自己增加了一些需要的字段。
实现Provider
有了一个Token,还需要一共支持此Token的AuthenticationProvider
,这个是一个接口,直接实现它就可以。
接口有两个方法,分别是
public Authentication authenticate(Authentication authentication)
throws AuthenticationException
这个方法用于验证Token,如果Token没有问题,应该返回一个验证完毕,含有用户信息的Token对象。
public boolean supports(Class<?> authentication)
这个方法是确定此Provider适用于那个Token,因此只需要对Token的class进行判断,然后返回true或false就可以。
实现Principal
这是一个简要的用户身份数据,它也是一个接口,就是一些getter和Setter,把需要的数据字段手动加到上面就可以。
实现Filter
这里说的filter主要分两种类型,一个是用于登录的LoginFilter,一个是用来验证的Filter。
登录用的Filter可以继承AbstractAuthenticationProcessingFilter
得到,这是一个抽象类,需要重新编写构造方法
构造方法:(SecurityService helper,AuthenticationManager manager)
这里面需要制定此Filter拦截的路径,通过父类的构造可以做到这一点,例如:
super(new AntPathRequestMatcher("/public/user/login","POST"));
另外一个方法是用来验证用户身份的:
public Authentication attemptAuthentication(HttpServletRequest request
, HttpServletResponse response)
还有这个,在认证成功的时候这个方法会被调用的。
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException
在成功验证了用户名和密码后,就应该签发一个可以证明用户身份的东西,这个东西用户以后访问系统都会携带在Http的请求头中,系统就可以通过这个来确定用户的身份以及权限。这就需要另外一个Filter来完成了。
这里我通过继承BasicAuthenticationFilter
这个Filter完成Token的验证,需要重写的方法是这样的:
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws IOException, ServletException
这个是Filter的过滤方法,可以通过request得到Http的请求头,然后在那里面寻找Token,如果有Token就要验证下,没有就让他继续doFilter。
Filter中的异常
安全验证过程中出现异常是很常见的,但是这里的异常对象是无法被ControllerAdvice
以及ExceptionHandler
捕获和处理的,一旦异常就会出现非常大的一堆报错信息,对于后续的调试非常不友好。
所以还是Try - Catch吧,木有很好的办法。
可以自己定义一些在安全验证中常出现的异常,做好log的同时抛出,就可以在Filter中有针对性的输出或者做些其他的处理。
配置Security到SpringBoot
通过继承WebSecurityConfigurerAdapter
可以得到一个用来配置SpringSecurity的Config类,首先在这里配置可以直接访问的URL和需要通过身份验证的URL:
这里需要两个Filter,就在这个config中直接把他们定义为Bean就可以了(使用@Bean
就可以做到)。
上述两个Filter是需要一个AuthenticationManager的,这个Manager就在config中,但是如果直接启动,Spring会找不到他,所以可以这样做:
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
这样AuthenticationManager也就成为了一个Bean了。
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() // 开始配置Request的URL
.antMatchers("/public/**") // 匹配此路径的
.permitAll() // 无需认证直接可以访问
.formLogin() // 登录页面
.loginPage("/public/user/login").permitAll() // 无需认证直接访问
.authorizeRequests() // 其他地址需要认证
.anyRequest().authenticated()
.addFilterBefore(LoginFilter() // 添加Filter
,UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(TokenFilter(),
,UsernamePasswordAuthenticationFilter.class);
}
这里面permitAll
允许访问,denyAll
禁止访问,authenticated
指的是要求认证,不认证身份会跳到登录界面。
然后需要在这里注入之前实现的Provider,在这个方法中把它注册到SpringSecurity中:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(provider);
}
到此,SpringSecurity配置就全部结束了。