开发者学堂课程【Spring Security知识精讲与实战演示(三):分布式整合之重写认证的过滤器】学习笔记与课程紧密联系,让用户快速学习知识
课程地址:https://developer.aliyun.com/learning/course/732/detail/13071
分布式整合之重写认证的过滤器
分布式整合之重写认证的过滤器
之前编写了RoleMapper和UserMapper,但是忘记在
AuthServerApplication.java 代码中加上 @MapperScan("com itheima. mapper"),这样之后就可以自动扫描并生成代理对象。
import com itheima.config.RsaKeyProperties;
import org.mybatis.spr ing. annotation. MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.
SpringBootApplication;
Import org.springframework.boot.context.proper
ties.EnableConfigurationProperties;
@SpringBootApplication
@MapperScan("com itheima. mapper")
@EnableConfigurationProperties(RsaKeyProperties.class)
public class AuthServerApplication {
public static void main(String[] args){
SpringApplication.run(AuthServerApplication.class, args);
}
}
另外,在RsaKeyProperties.java中不需要在
@ConfigurationProperties("rsa.key")前加入@Configuratio,因为声明了配置类就意味着当前对象不放入aoc容器中,而我们需要将当前对象放入容器中,故不需要这个注解。
import javax. annotation. PostConstruct;
import java. security. PrivateKey;
import java. security. PublicKey;
@ConfigurationProperties("rsa.key") .
public class RsaKeyProperties {
private Str ing pubKeyFile;
private String pr KeyFile;
private PublicKey publicKey;
private PrivateKey privateKey;
接下来,重新定义认证方法:
创建一个新的过滤器,名称为 filter.JwtTokenFilter 或者 filter.JwtLoginFilter,如下图。
package com. itheima.filter ;
public class JwtLoginFilter
{
}
在 JwtLoginFilter 里,需要对默认的 UsernamePasswordluthenticationFilter 进行继承,继承的目的是为了重写其中的两个方法。
package com. itheima.filter ;
import org.springframework. security.web. authentic
ation. UsernamePasswordluthenticationFilter;
public class JwtLoginFilter extends UsernamePassword
AuthenticationFilter
{
}
第一是认证逻辑,内容如下
public Authentication attemptAuthentication(HttpS
ervletRequest request,HttpServletResponse response) throw
s
if (this.postOnly && !request.getMethod().equals("PO
ST"))
{
throw new AuthenticationServiceException("Authentic
ation method not supported: " + request.getMethod
} else {
String username = this.obtainUsername (request);
String password = this.obtainPassword(reqhest);
if (username == null){
username =
“”;
}
if (password == nul
l
) {
password = "";
}
username = username.trim() ;
UsernamePassworduthenticationToken authRequest = new UsernamePasswordAuthenticationToken(usern
ame
,
this.setDetails(request,authRequest);
return this.getAuthenticationManager (). Authenticate
(authRequest);
}
}
将上述内容粘贴进JwtLoginFilte,只需要留return一句:
Import org.springframework.security.core.Authentic
ationException;
import org.springframework.security.web. authen
tication. UsernamePassworduthenticationFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse ;
public class JwtLoginFilter extends UsernamePasswordAuthenticationFilter {
public Authentication attempt;huthentication(HttpS
ervletRequest request,HttpServrletResponse response) throws Authentica
n
r
eturn this.getAuthenticationManager(). Authenticate
(authRequest);
}
}
authRequest
是U
sernamePassvordauthenticationI
Token authRequest = new UsernamePassword
Quthentication
Token(username
,
password)
;
之后将这个token粘进JwtLoginFilte:
public Authentication attempt;huthentication(HttpS
ervletRequest request,HttpServrletResponse response) throws Authentica
n
U
sernamePassvordauthenticationIToken authRequest = new UsernamePasswordquthentication
Token(username,password)
;
r
eturn this.getAuthenticationManager(). Authenticate
(authRequest);
}
}
这个token里需要传入 username和password,在这里可以直接从请求体中解析出来;getAuthenticationManager()对象是要 AuthenticationManager:
public class JwtLoginFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
注:以上代码是没有值的,需要生成一个带参数的构造方法。
private RsaKeyProperties prop;
public JwtLoginFilter(AuthenticationManager authenticationManager,RsaKeyProperties prop)
{
this.authenticationManager = authenticationManager ;
this.prop = prop;
}
public Authentication attempt;huthentication(HttpS
ervletRequest request,HttpServrletResponse response) throws Authentica
nException
{
注:获取username和password,这里是有异常的,要进行try/catch Block操作。
T
ry
{
SysUser sysUser = new ObjectMapper().readValue(request.getInputStream(),SysUser.class);
U
sernamePassvordauthenticationIToken authRequest = new UsernamePasswordquthenticationToken(sysUser.getUsername
()
, sysUser.getPassword
()
)
;
r
eturn this.getAuthenticationManager(). Authenticate
(authRequest);
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
得到用户对象之后,username和password改为sysUser.getUsername(), sysUser.getPassword(),且这两个数据都是用户输入过来的
接下来可以输入throw new RuntimeException(e)将异常抛出去
注意,在这个代码中是异步请求并未跳转任何界面,如果没有成功,要提示用户,直接抛异常用户是看不见的,可以进行以下操作提示用户。
r
eturn this.getAuthenticationManager(). Authenticate
(authRequest);
}
catch (Exception e)
{
try {
//登陆成功时,返回JSON格式进行提示
response.setContentType("application/json; charset=
utf-8"");
response. setStatus (HttpServletResponse.SC_UNAUT
HORIZED);
PrintWriter out = response.getWriter() ;
Map resultMap = new HashMap();
resultMap.put("code",HttpServletResponse.SC_UNAU
THORIZED);
resultMap. put("msg",
"用户名或密码错误!");
out.write(new 0bjectMapper ().writeValueAsString (resultMapp )
;
out.flush() ;
out.c
lose(
);
throw new RuntimeException(e);
} catch (Exception outEx)【
outEx.printStackTrace() ;
}
throw new RuntimeException(e);
}
}
到此,上面的认证逻辑完成,其会根据后续的认证操作进行判断,用户名或密码正确则认证通过,认证失败则显示“用户名或密码错误”;如果认证成功,会继续执行一段代码,这段代码写在
UsernamePasswordAuthenticationFilter
过滤器的副类中:
Protected voids uccessfulkuthentication(HttpServlet
Request request,HittpServletResponse response,FilterChain chain, Authenti
can
Exception();
if (this. logger.isDebugEnabled()
)
{
this. logger. debug (
o:
"Authentication success.Upda
ting SecurityContextHolder to contain:" + authResult);
}
SecurityContextHolder.getContext(.setAuthentication(authResult);
this.rememberMeServices.loginSuccess(request,response,authResult);
if (this.eventPublisher != null){
this.eventPublisher. publishEvent(new Interactive
AuthenticationSuccessEvent(authResult,this.getClass()>);
this. successHandler. onAuthenticationSuccess(request,response,authResult);
}
将上述内容中
Protected voids uccessfulkuthentication(HttpServlet
Request request,HittpServletResponse response,FilterChain chain, Authentication aut
h
Result)throws I0Exception,ServletException
;
的粘进JwtLoginFilte,之后这下面要生成token:
Protected voids uccessfulkuthentication(HttpServlet
Request request,HittpServletResponse response,FilterChain chain, Authenti
can
Exception();
String token = JwtUtils.generateTokenExpire InMinutes(userT prop.getPrivateKey(),expire:24* 60) ;
}
}
注:generateTokenExpire InMinutes 里的Object usqr Info,代表自己定义的用户对象;PrivateKey privateKey,代表加密需要使用的私钥;int expire),代表过期时间
现在这里的user对象是不存在的,下面创建user对象:
Protected voids uccessfulkuthentication(HttpServlet
Request request,HittpServletResponse response,FilterChain chain, Authenti
can
Exception();
SysUser user = new SysUser();
user.setUsername(authResult.getName()) ;
user.setRoles((List<SysRole>) authResult.getAuthorities();
String token = JwtUtils.generateTokenExpire InMinutes(userT prop.getPrivateKey(),expire:24* 60) ;
}
}
至此,token完成,需要将token写回给请求者,可以将其写回到请求头里面,里面的名称可以随便取,但一般都为"Authorization","Bearer" + token
。
String token = JwtUtils.generateTokenExpire InMinutes(userT prop.getPrivateKey(),expire:24* 60) ;
res.addHeader("Authorization","Bearer" + token);
try {
//登陆成功时,返回JSON格式进行提示
response.setContentType("application/json; charset=
utf-8"");
response. setStatus (HttpServletResponse.SC_
OK
);
PrintWriter out = response.getWriter() ;
Map resultMap = new HashMap();
resultMap.put("code",HttpServletResponse.SC_
OK
);
resultMap. put("msg","
认证通过!");
out.write(new 0bjectMapper ().writeValueAsString (resultMapp )
;
out.flush() ;
out.c
lose(
);
throw new RuntimeException(e);
} catch (Exception outEx)【
outEx.printStackTrace() ;
}
}
认证成功之后回写 token 操作完成。