1.开始
因为最近在做第二课堂项目遇到了一个问题,已经登陆的用户虽然没有对其开放管理员界面但是只要他用token请求接口还是会响应结果,没有达到权限管理的要求。于是我学习了Spring Security用它配合JWT达到认证和授权的目的。
主要依赖
<!--security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--jjwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
2. 创建security可识别的角色类
public class RoleUser implements UserDetails {
String password; //密码
String username; //用户名
boolean accountNonExpired; //是否没过期
boolean accountNonLocked; //是否没被锁定
boolean credentialsNonExpired; //是否没过期
boolean enabled; //账号是否可用
Collection<? extends GrantedAuthority> authorities; //用户的权限集合
public RoleUser(){}
public RoleUser(String password, String username, boolean accountNonExpired, boolean accountNonLocked, boolean credentialsNonExpired, boolean enabled, Collection<? extends GrantedAuthority> authorities) {
this.password = password;
this.username = username;
this.accountNonExpired = accountNonExpired;
this.accountNonLocked = accountNonLocked;
this.credentialsNonExpired = credentialsNonExpired;
this.enabled = enabled;
this.authorities = authorities;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return enabled;
}
public void setPassword(String password) {
this.password = password;
}
public void setUsername(String username) {
this.username = username;
}
public void setAccountNonExpired(boolean accountNonExpired) {
this.accountNonExpired = accountNonExpired;
}
public void setAccountNonLocked(boolean accountNonLocked) {
this.accountNonLocked = accountNonLocked;
}
public void setCredentialsNonExpired(boolean credentialsNonExpired) {
this.credentialsNonExpired = credentialsNonExpired;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
this.authorities = authorities;
}
}
3.生成可识别对象
public class UserRoleServiceImpl implements UserRoleService {
@Resource
private UserMapper userMapper;
private final String ROLE_ADMIN="admin";
private final String ROLE_USER="user";
@Override
public UserDetails loadUserByUsername(String uid) throws UsernameNotFoundException {
User u = userMapper.selectByPrimaryKey(Integer.parseInt(uid));
System.out.println(u);
RoleUser roleUser=null;
if(ROLE_ADMIN.equals(u.getuLevel())){
roleUser= new RoleUser(u.getuPass(),uid , true, true, true, true,
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_ADMIN"));
}
if (ROLE_USER.equals(u.getuLevel())){
roleUser= new RoleUser(u.getuPass(),uid , true, true, true, true,
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
}
return roleUser;
}
}
4.配置 SecurityConfig
@Configuration
@EnableWebSecurity //启动security过滤器链
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private UserRoleServiceImpl userRoleServiceImpl;
@Resource
private DataSource datasource;
@Resource
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
private static String[] WHITE_LIST={};
/**
*密码加密
*/
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
/**
*从数据库加载
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userRoleServiceImpl).passwordEncoder(new BCryptPasswordEncoder());
}
/**
*静态资源
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/js/**", "/css/**", "/images/**","/fileUpload/**");
}
/**
*继承权限
*/
@Bean
RoleHierarchy roleHierarchy() {
RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
hierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");
return hierarchy;
}
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
tokenRepository.setDataSource(datasource);
//tokenRepository.setCreateTableOnStartup(true); // 第一次使用此功能自动创建表,第二次要关闭,否则报错
return tokenRepository;
}
/**
*数据库中需要添加前缀ROLE_,hasAnyRole可以识别前缀后的内容
*
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and()
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers(WHITE_LIST).permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasRole("USER")
.anyRequest().authenticated()//authenticated()要求登录
// 不需要session,开启session就直接使用JSESSIONID操作了
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.rememberMe()
.rememberMeParameter("remember-me-new")
.rememberMeCookieName("remember-me-cookie")
.tokenValiditySeconds(2 * 24 * 60 * 60)//2天
.tokenRepository(persistentTokenRepository())
.and().csrf().disable();
}
@Bean(name = BeanIds.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
5.jwt工具类
@Data
@Component
public class JwtTokenUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
@Value("${jwt.header}")
private String header;
//建议更换时间
private final Integer ADVANCE_EXPIRE_TIME=60000;
/**
* 生成token令牌
*
* @param username 用户
* @return 令token牌
*/
public String generateToken(String username) {
Map<String, Object> claims = new HashMap<>(2);
claims.put("sub",username);
claims.put("created", new Date());
return generateToken(claims);
}
/**
* 从令牌中获取用户名
*
* @param token 令牌
* @return 用户名
*/
public String getUsernameFromToken(String token) {
String username;
try {
Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
}
return username;
}
/**
* 判断令牌是否过期
*
* @param token 令牌
* @return 是否过期
*/
public Boolean isTokenExpired(String token) {
try {
Claims claims = getClaimsFromToken(token);
Date expiration = claims.getExpiration();
return expiration.before(new Date());
} catch (Exception e) {
return false;
}
}
/**
* 刷新令牌
*
* @param token 原令牌
* @return 新令牌
*/
public String refreshToken(String token) {
String refreshedToken;
try {
Claims claims = getClaimsFromToken(token);
claims.put("created", new Date());
refreshedToken = generateToken(claims);
} catch (Exception e) {
refreshedToken = null;
}
return refreshedToken;
}
/**
* 验证令牌
*
* @param token 令牌
* @param userDetails 用户
* @return 是否有效
*/
public Boolean validateToken(String token, UserDetails userDetails) {
String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
/**
* 从claims生成令牌,如果看不懂就看谁调用它
*
* @param claims 数据声明
* @return 令牌
*/
private String generateToken(Map<String, Object> claims) {
Date expirationDate = new Date(System.currentTimeMillis() + expiration);
return Jwts.builder().setClaims(claims)
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
/**
* 从令牌中获取数据声明,如果看不懂就看谁调用它
*
* @param token 令牌
* @return 数据声明
*/
private Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
} catch (Exception e) {
claims = null;
}
return claims;
}
/**
* 检查当前token是否还能继续使用
* true:可以 false:不建议
* @param token
* @return
*/
public boolean checkToken(String token){
try {
// jwt正常情况 则判断失效时间是否大于5分钟
long expireTime = Jwts.parser() //得到DefaultJwtParser
.setSigningKey(secret) //设置签名的秘钥
.parseClaimsJws(token.replace("jwt_", ""))
.getBody().getExpiration().getTime();
long diff = expireTime - System.currentTimeMillis();
System.out.println(diff);
//如果有效期小于5分钟,则不建议继续使用该token
if (diff < ADVANCE_EXPIRE_TIME) {
return false;
}
} catch (Exception e) {
return false;
}
return true;
}
}
6.登录认证换取JWT令牌
public String login(String username,String password) throws CustomException {
try {
UsernamePasswordAuthenticationToken upToken =
new UsernamePasswordAuthenticationToken(username, password);
Authentication authentication = authenticationManager.authenticate(upToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
}catch (AuthenticationException e){
throw new CustomException(CustomExceptionType.USER_INPUT_ERROR
,"用户名或者密码不正确");
}
// UserDetails userDetails = userDetailsService.loadUserByUsername(username);
return jwtTokenUtil.generateToken(username);
}
7.JwtAuthenticationTokenFilter过滤
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Resource
JwtTokenUtil jwtTokenUtil;
@Resource
private UserRoleServiceImpl userRoleServiceImpl;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String jwtToken = request.getHeader(jwtTokenUtil.getHeader());
if(!StringUtils.isEmpty(jwtToken)){
//校验合法性并提取用户名
String username = jwtTokenUtil.getUsernameFromToken(jwtToken);
//合法,且未验证过
if(username != null &&
SecurityContextHolder.getContext().getAuthentication() == null){
UserDetails userDetails = userRoleServiceImpl.loadUserByUsername(username);
//是否过期
if(jwtTokenUtil.validateToken(jwtToken,userDetails)){
if (!jwtTokenUtil.checkToken(jwtToken)){
//更换令牌
response.setHeader("Access-Control-Expose-Headers",jwtTokenUtil.getHeader());
response.setHeader(jwtTokenUtil.getHeader(),jwtTokenUtil.refreshToken(jwtToken) );
}
//给使用该JWT令牌的用户进行授权
UsernamePasswordAuthenticationToken authenticationToken
= new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());
//交给Security管理
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
}
//过滤器链继续走下去
filterChain.doFilter(request,response);
}
}