spring-security总结
今天公司大佬给我讲了一下security心里一直默默的喊666,哈哈哈,赶快写个笔记别等会儿忘,闲话不多说,开始写码代码。
- 写好拦截配置
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private UserDetailsService userDetailsService;
public WebSecurityConfig(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
// 设置 HTTP 验证规则
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().authorizeRequests()
// 允许对于网站静态资源的无授权访问
.antMatchers(
HttpMethod.GET,
"/",
"/*.html",
"/**/*.html",
"/**/*.css",
"/**/*.js"
).permitAll()
.antMatchers(HttpMethod.GET, "/captcha/get").permitAll() //验证码
.anyRequest().authenticated()
.and()
.addFilter(new JWTLoginFilter(authenticationManager()))
.addFilter(new JWTAuthenticationFilter(authenticationManager()));
//以下这句就可以控制单个用户只能创建一个session,也就只能在服务器登录一次
http.sessionManagement().maximumSessions(1).expiredUrl("/login");
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
// 使用自定义身份验证组件
auth.authenticationProvider(new CustomAuthenticationProvider(userDetailsService));
}
}
配置好了拦截器security会自动拦截url为/login的操作,并进行登录操作的相关验证
- 书写拦截类,这个进行了身份认证
public class JWTLoginFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
public JWTLoginFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
// 接收并解析用户凭证
@Override
public Authentication attemptAuthentication(HttpServletRequest req,
HttpServletResponse res) throws AuthenticationException {
try {
LoginParam user = new ObjectMapper()
.readValue(req.getInputStream(), LoginParam.class);
String captcha = user.getCaptcha();
//将验证码加入到session中
String session_captcha = (String) req.getSession().getAttribute(Consts.IMAGE_CODE);
if(StringUtils.isEmpty(captcha)){
throw new YcException(RespCode.PARAM_ERROR, "验证码为空");
}else if(!captcha.toLowerCase().equals(session_captcha.toLowerCase())){
throw new YcException(RespCode.PARAM_ERROR, "验证码错误");
}
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
user.getUsername(),
user.getPassword(),
new ArrayList<>())
);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// 用户成功登录后,这个方法会被调用,我们在这个方法里生成token
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
String token = Jwts.builder()
.setSubject(authResult.getName())
.setExpiration(new Date(System.currentTimeMillis() + Consts.JWT_EXPIRE)) //失效时间
.signWith(SignatureAlgorithm.HS512, Consts.SECRET)
.compact();
response.addHeader(Consts.AUTH_KEY, JwtUtils.getTokenHeader(token));
String result = JSONObject.toJSONString(new ResponseInfo());
PrintWriter writer = response.getWriter();
writer.write(result);
}
}
- 写自定义身份验证的自定义类,同时生成token
public class CustomAuthenticationProvider implements AuthenticationProvider {
private UserDetailsService userDetailsService;
public CustomAuthenticationProvider(UserDetailsService userDetailsService){
this.userDetailsService = userDetailsService;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// 获取认证的用户名 & 密码
String name = authentication.getName();
String password = authentication.getCredentials().toString();
// 认证逻辑
UserDetails userDetails = userDetailsService.loadUserByUsername(name);
if(null != userDetails){
if(MD5Util.verify(password,userDetails.getPassword())){
// 生成令牌,返回Authentication对象上设置的授予权限是空的 因为权限是特定于应用程序的
//在这一步可以进行数据库查询,然后将查询的权限信息加入到Authentication 中
Authentication auth = new UsernamePasswordAuthenticationToken(name, password, Collections.emptyList());
return auth;
}else {
throw new BadCredentialsException("密码错误~");
}
}else {
throw new UsernameNotFoundException("用户不存在~");
}
}
// 是否可以提供输入类型的认证服务
@Override
public boolean supports(Class> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
- 写UserDetailsService的实现类
@Service
public class JwtUserDetailsService implements UserDetailsService {
@Reference(version = "1.0.0", check = false, timeout = 10000)
IUserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.findByAccount(username);
if (user == null) {
throw new UsernameNotFoundException(String.format("No user found with username '%s'.", username));
} else {
return new org.springframework.security.core.userdetails.User(user.getAccount(), user.getPassword(),emptyList());
}
}
}
这个类要实现UserDetailsService类接口,实现这个类的目的是将用户名密码交给security管理起来。
- 书写登录操作的实体类,注意要这个实体类要实现UserDetails接口,实现它的目的也是为了让spring-security管理起来user实体类,在JWTLoginFilter这个中
LoginParam user = new ObjectMapper().readValue(req.getInputStream(), LoginParam.class);
可以在流中读取出用户对象就是因为这个用户登录对象实现现UserDetailsService这个类被spring管理起来这个实体
@Data
public class LoginParam implements UserDetails, Serializable{
@NotBlank(message = "username 不能为空")
private String username;
@NotBlank(message = "password 不能为空")
private String password;
private String captcha;
@Override
public Collection extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public String toString() {
return this.username;
}
@Override
public int hashCode() {
return username.hashCode();
}
@Override
public boolean equals(Object rhs) {
return rhs instanceof User ? this.username.equals(((User) rhs).getUsername()) : false;
}
}
- 写授权类
public class JWTAuthenticationFilter extends BasicAuthenticationFilter {
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String header = request.getHeader(Consts.AUTH_KEY);
if (header == null || !header.startsWith(JwtUtils.getAuthorizationHeaderPrefix())) {
chain.doFilter(request, response);
return;
}
UsernamePasswordAuthenticationToken authenticationToken = getUsernamePasswordAuthenticationToken(header);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
chain.doFilter(request, response);
}
private UsernamePasswordAuthenticationToken getUsernamePasswordAuthenticationToken(String token) {
String user = Jwts.parser()
.setSigningKey(Consts.SECRET)
.parseClaimsJws(token.replace(JwtUtils.getAuthorizationHeaderPrefix(), ""))
.getBody()
.getSubject();
if (null != user) {
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
}
return null;
}
}
可能在这里会有疑惑,在这里的操作没有进行数据库的查询啊,框架是怎么判断用户名和密码是否配呢?因为我们在JwtUserDetailsService已经把用户名和密码交给spring来管理了。
return new org.springframework.security.core.userdetails.User(user.getAccount(), user.getPassword(),emptyList());