spring security登入和鉴权
1. User
@Entity @Table(name = "user") public class User implements UserDetails{ /** * */ private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY)// 自增长策略 private Integer id; @Column private String username; @Column @JsonIgnore private String password; @Column private String phone; // JPA 的规范要求无参构造函数;设为 protected 防止直接使用 protected User() { } public User(String username, String password) { this.username = username; this.password = password; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { // TODO Auto-generated method stub return null; } @Override @JsonIgnore public boolean isAccountNonExpired() { return true; } @Override @JsonIgnore public boolean isAccountNonLocked() { return true; } @Override @JsonIgnore public boolean isCredentialsNonExpired() { return true; } @Override @JsonIgnore public boolean isEnabled() { return true; } }
2. static类
public class SecurityConstants { public final static String AUTHORIZATION = "Authorization"; public final static String AUTHORIZATION_PRE = "Bearer"; public final static String AUTH_SIGNING_KEY = "MySecret"; public final static Integer SC_OK = 200; public final static Integer SC_ERROR_USER_OR_PASS = 201; //用户名或密码错误 public final static Integer SC_JWT_EXPIRED = 202; //token过期 public final static Integer SC_JWT_NO_AUTH = 203; //用户无权限 public final static Integer SC_JWT_NO_TOKEN = 204; //缺少token public final static Integer SC_JWT_INVALID_TOKEN = 205; //无效token(username为空) public final static Integer SC_JWT_MALFORMED = 206; //token格式错误 public final static Integer SC_JWT_ERROR_SIGNATURE = 207; //token不匹配 }
3. 配置WebSecurityConfigurerAdapter
extends
@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
private UserDetailsService udService;
@Autowired
private JupiterLogoutSuccessHandler jLogoutSuccessHandler;
}
override
@Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(udService).passwordEncoder(new BCryptPasswordEncoder()); }
override
@Override protected void configure(HttpSecurity http) throws Exception { http.cors()//允许跨域,在filter中也设置了,这里应该可以不设置 .and().csrf().disable()//csrf针对的是session的攻击,使用jwt就不需要这个保护 // 基于token,所以不需要session .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and().logout().logoutUrl("/logout").logoutSuccessHandler(jLogoutSuccessHandler) .and() .addFilter(new JWTLoginFilter(super.authenticationManager())) .addFilter(new JWTAuthenticationFilter(super.authenticationManager()));
//.and().authorizeRequests().antMatchers(HttpMethod.GET,"/**").permitAll()//这样配置无效,还是web.ignoring可以生效
//.antMatchers("/user/signup","/h2db/**").permitAll()
}
override
/** * 配置需要放行的接口 */ @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers(HttpMethod.GET,"/**") .antMatchers("/user/signup","/h2db/**"); }
4. 配置登入UsernamePasswordAuthenticationFilter
extends
public class JWTLoginFilter extends UsernamePasswordAuthenticationFilter{
private AuthenticationManager authenticationManager;
public JWTLoginFilter() {
}
public JWTLoginFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
}
Override
@Override public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException {try { String s = IOUtils.toString(req.getInputStream(), StandardCharsets.UTF_8); JSONObject j = JSON.parseObject(s); String username = j.getString("username"); String psw = j.getString("password"); UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username.trim(),psw); return authenticationManager.authenticate(authToken); } catch (IOException e) { throw new RuntimeException(e); } }
Override
/** * 用户名密码匹配成功后,这个方法会被调用,生成token */ @Override protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain, Authentication auth) throws IOException, ServletException { User user = (User)auth.getPrincipal();//生成token Map<String,Object> sub = new HashMap<String,Object>(); sub.put("username", user.getUsername());//在token中保存id方便后续使用// String token = Jwts.builder() .setSubject(JsonUtil.ObjectToJson(sub)) .setExpiration(new Date(System.currentTimeMillis() + 60 * 1000 * 60 * 24))//24h: 60 * 1000 * 60 * 24 .signWith(SignatureAlgorithm.HS512, SecurityConstants.AUTH_SIGNING_KEY) .compact(); res.setStatus(SecurityConstants.SC_OK); //允许前端获取自定义的header res.setHeader("Access-Control-Expose-Headers", "Authorization,username");//,rolename,department //装载自定义key(前端需要decode) String name = URLEncoder.encode(user.getUsername(), "UTF-8").replaceAll("\\+", "%20"); // res.addHeader(SecurityConstants.AUTHORIZATION, SecurityConstants.AUTHORIZATION_PRE + token); res.addHeader("username", name); }
Override
/** * 用户名不存在时,虽然在UserDetailsServiceImpl中抛出了UsernameNotFoundException * 但被别的方法捕获并且重新抛出BadCredentialsException */ @Override protected void unsuccessfulAuthentication(HttpServletRequest request,HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {if(failed instanceof UsernameNotFoundException) { log.info("---unsuccessfulAuthentication---- is UsernameNotFound"); } log.info("--JWTLoginFilter-unsuccessfulAuthentication----"+failed.getLocalizedMessage()); log.info("--JWTLoginFilter-unsuccessfulAuthentication----"+failed.getMessage()); log.info("--JWTLoginFilter-unsuccessfulAuthentication----"+failed.getCause()); log.info("--JWTLoginFilter-unsuccessfulAuthentication----"+failed.getStackTrace()); response.setStatus(SecurityConstants.SC_ERROR_USER_OR_PASS); }
implements
@Service public class UserDetailsServiceImpl implements UserDetailsService{ @Autowired private UserRepository userRep; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRep.findByUsername(username);if(StringUtil.isNull(user)) {throw new UsernameNotFoundException("username NOT found"); } return user; } }
5. 鉴权类BasicAuthenticationFilter
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 token = request.getHeader(SecurityConstants.AUTHORIZATION);//不区分大小写 if (StringUtil.isNull(token) || !token.startsWith(SecurityConstants.AUTHORIZATION_PRE)) { response.setStatus(SecurityConstants.SC_JWT_NO_TOKEN); return; } //验证token try { Jwts.parser().setSigningKey(SecurityConstants.AUTH_SIGNING_KEY) .parseClaimsJws(token.replace(SecurityConstants.AUTHORIZATION_PRE, "")) .getBody().getSubject();
//如果需要获取sub,再从sub中获取之前设置的字典
//String sub= .getSubject(); }catch(ExpiredJwtException e) { response.setStatus(SecurityConstants.SC_JWT_EXPIRED);//过了期限 return; }catch(MalformedJwtException e) { response.setStatus(SecurityConstants.SC_JWT_MALFORMED);//token格式错误 return; }catch(SignatureException e) { response.setStatus(SecurityConstants.SC_JWT_ERROR_SIGNATURE);//token不匹配 return; } //继续其他的filter chain.doFilter(request, response); } }
6. 登出类
@Component public class JupiterLogoutSuccessHandler implements LogoutSuccessHandler{ @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { } }