微服务大行其道的现在,如果我们还在用wsdl之类的提供接口,给人的感觉就会很low,虽然说不能为了炫技而炫技,但是既然restful接口已经越来越流行,必然有它的道理。 本文我们不讨论restful接口的好处,旨在解决使用restful时候的权限控制问题。
springboot本身已经提供了很好的spring security的支持,我们只需要实现(或者重写)一部分接口来实现我们的个性化设置即可。本文浅显易懂,没有深入原理(后面文章会将,有需要的小伙伴稍等等~~~)。 思路: 1.通过spring security做授权拦截操作 2.通过jwt根据用户信息生成token以供后面调用 3.将生成的token放到HttpServletResponse头信息中 4.使用的时候从response头中获取token放在request头中提交到后台做认证即可 5.默认超时时间10天
一、pom
惯例还是先上pom,因为pom可以很直观的看到本项目用了哪些东西,我这个项目使用了很多的包,这里贴出了核心的几个,其他大部分的都会自动引用。
-
<dependency> -
<groupId>org.springframework.boot</groupId> -
<artifactId>spring-boot-starter-security</artifactId> -
</dependency> -
<dependency> -
<groupId>org.springframework.security</groupId> -
<artifactId>spring-security-web</artifactId> -
<scope>provided</scope> -
</dependency> -
<dependency> -
<groupId>org.springframework.security</groupId> -
<artifactId>spring-security-config</artifactId> -
<scope>provided</scope> -
</dependency> -
<dependency> -
<groupId>io.jsonwebtoken</groupId> -
<artifactId>jjwt</artifactId> -
</dependency>
二、登录过滤器
我们采用倒推法,用到什么找什么,这也比较符合XP编程的思想,不写多(无)余(用)的代码,既然要做认证,很明显需要一个过滤器来处理所有需要拦截的请求。 UsernamePasswordAuthenticationFilter是security自己提供的过滤器,我们重写其中的成功方法(successfulAuthentication)来处理我们自己的逻辑,当然根据自己的情况,比如登录失败处理,重写(unsuccessfulAuthentication)即可。 1.成功回调中用到一个TokenAuthenticationHandler,即token认证处理类,该类的主要方法就是借用jwt的机制来生成token,以供后面登录授权使用。 2.往response头信息中放入参数为“Authorization”,值为“Bearer ”+token的值
-
package com.mos.eboot.tools.jwt; -
import com.mos.eboot.tools.util.FastJsonUtils; -
import org.springframework.security.authentication.AuthenticationManager; -
import org.springframework.security.core.Authentication; -
import org.springframework.security.core.userdetails.UserDetails; -
import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -
import javax.servlet.FilterChain; -
import javax.servlet.ServletException; -
import javax.servlet.http.HttpServletRequest; -
import javax.servlet.http.HttpServletResponse; -
import java.io.IOException; -
public class JWTLoginFilter extends UsernamePasswordAuthenticationFilter{ -
static final String TOKEN_PREFIX = "Bearer"; -
static final String HEADER_STRING = "Authorization"; -
private AuthenticationSuccessHandler successHandler; -
public JWTLoginFilter() { -
} -
public JWTLoginFilter(AuthenticationManager authManager) { -
setAuthenticationManager(authManager); -
} -
@Override -
protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain, Authentication auth) throws IOException, ServletException { -
TokenAuthenticationHandler tokenAuthenticationHandler = new TokenAuthenticationHandler(); -
Object obj = auth.getPrincipal(); -
if(obj != null) { -
UserDetails userDetails = (UserDetails)obj; -
String token = tokenAuthenticationHandler.generateToken(FastJsonUtils.toJSONNoConfig(userDetails)); -
res.addHeader(HEADER_STRING, TOKEN_PREFIX + " " + token); -
} -
if(successHandler != null) { -
successHandler.onAuthenticationSuccess(req, res, auth); -
} -
} -
public void setSuccessHandler(AuthenticationSuccessHandler successHandler) { -
this.successHandler = successHandler; -
} -
}
JWTAuthenticationToken
-
package com.mos.eboot.tools.jwt; -
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -
import static java.util.Collections.emptyList; -
public class JWTAuthenticationToken extends UsernamePasswordAuthenticationToken{ -
private static final long serialVersionUID = 1L; -
public JWTAuthenticationToken(Object principal) { -
super(principal,null,emptyList()); -
} -
@Override -
public Object getCredentials() { -
return super.getCredentials(); -
} -
@Override -
public Object getPrincipal() { -
return super.getPrincipal(); -
} -
}
TokenAuthenticationHandler
-
package com.mos.eboot.tools.jwt; -
import io.jsonwebtoken.Claims; -
import io.jsonwebtoken.Jwts; -
import io.jsonwebtoken.SignatureAlgorithm; -
import java.io.Serializable; -
import java.util.Date; -
import java.util.HashMap; -
import java.util.Map; -
/** -
* @author 小尘哥 -
*/ -
public class TokenAuthenticationHandler implements Serializable { -
private static final long serialVersionUID = 1L; -
private static final String CLAIM_KEY_CREATED = "created"; -
private static final String CLAIM_KEY_SUBJECT = "subject"; -
private static final String DEFAULT_SECRET = "eboot@secret"; -
private static final Long DEFAULT_EXPIRATION = 864000L; -
private String secret = DEFAULT_SECRET; -
private Long EXPIRATION = DEFAULT_EXPIRATION; -
public TokenAuthenticationHandler() { -
} -
public String getSubjectFromToken(String token) { -
String subject; -
try { -
final Claims claims = getClaimsFromToken(token); -
subject = claims.get(CLAIM_KEY_SUBJECT).toString(); -
} catch (Exception e) { -
subject = null; -
} -
return subject; -
} -
private Claims getClaimsFromToken(String token) { -
Claims claims; -
try { -
claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); -
} catch (Exception e) { -
claims = null; -
} -
return claims; -
} -
private Date generateExpirationDate() { -
return new Date(System.currentTimeMillis() + EXPIRATION * 1000); -
} -
public String generateToken(String subject) { -
Map<String, Object> claims = new HashMap<String, Object>(); -
claims.put(CLAIM_KEY_CREATED, new Date()); -
claims.put(CLAIM_KEY_SUBJECT, subject); -
return generateToken(claims); -
} -
String generateToken( Map<String, Object> claims) { -
return Jwts.builder().setClaims(claims).setExpiration(generateExpirationDate()) -
.signWith(SignatureAlgorithm.HS512, secret).compact(); -
} -
}
三、认证
从头信息中取出Authorization,然后解析出个人信息,如果个人信息不为空,则将个人信息加密后再放入授权域。
-
package com.mos.eboot.tools.jwt; -
import org.apache.commons.lang3.StringUtils; -
import org.springframework.security.core.context.SecurityContextHolder; -
import org.springframework.web.filter.GenericFilterBean; -
import javax.servlet.FilterChain; -
import javax.servlet.ServletException; -
import javax.servlet.ServletRequest; -
import javax.servlet.ServletResponse; -
import javax.servlet.http.HttpServletRequest; -
import java.io.IOException; -
public class JWTAuthenticationFilter extends GenericFilterBean { -
static final String HEADER_STRING = "Authorization"; -
static final String TOKEN_PREFIX = "Bearer"; -
@Override -
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { -
HttpServletRequest req = (HttpServletRequest)request; -
String token = req.getHeader(HEADER_STRING); -
if(StringUtils.isNotBlank(token) && token.startsWith(TOKEN_PREFIX)) { -
TokenAuthenticationHandler tokenAuthenticationHandler = new TokenAuthenticationHandler(); -
String subject = tokenAuthenticationHandler.getSubjectFromToken(token.replace(TOKEN_PREFIX, "")); -
if(StringUtils.isNotBlank(subject)) { -
SecurityContextHolder.getContext().setAuthentication(new JWTAuthenticationToken(subject)); -
} -
} -
filterChain.doFilter(request,response); -
} -
}
四、调用
-
@Override -
protected void configure(HttpSecurity http) throws Exception { -
http.csrf().disable().authorizeRequests().antMatchers("/**").authenticated() -
.antMatchers(HttpMethod.POST, "/login").permitAll().anyRequest().permitAll().and() -
.addFilterBefore(loginFilter(), UsernamePasswordAuthenticationFilter.class) -
.addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); -
} -
//注入登录校验类 -
@Bean -
public JWTLoginFilter loginFilter() throws Exception { -
JWTLoginFilter loginFilter = new JWTLoginFilter(authenticationManager()); -
loginFilter.setSuccessHandler(loginAuthenticationSuccessHandler); -
loginFilter.setAuthenticationFailureHandler((request, response, exception) -> { -
response.setContentType("application/json"); -
response.getWriter().write(FastJsonUtils -
.toJSONString(new ResultModel(ResultStatus.FAIL.getCode(), exception.getMessage()))); -
}); -
return loginFilter; -
} -
@Bean -
public DaoAuthenticationProvider authenticationProvider() { -
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider(); -
authenticationProvider.setUserDetailsService(userDetailsService()); -
authenticationProvider.setPasswordEncoder(passwordEncoder()); -
authenticationProvider.setHideUserNotFoundExceptions(false); -
return authenticationProvider; -
} -
@Bean -
@Override -
public UserDetailsService userDetailsService() { -
return new UserService(); -
} -
//重写密码加密方法 -
@Bean -
public Md5PasswordEncoder passwordEncoder() { -
Md5PasswordEncoder passwordEncoder = new Md5PasswordEncoder(); -
passwordEncoder.setIterations(1); -
return passwordEncoder; -
}
实现UserDetailsService 接口,定义自己的获取用户登录方法实现类
-
package com.mos.eboot.api.config.support; -
import com.mos.eboot.api.platform.api.ISysUserService; -
import com.mos.eboot.platform.entity.SysUser; -
import com.mos.eboot.tools.result.ResultModel; -
import org.springframework.security.core.userdetails.UserDetails; -
import org.springframework.security.core.userdetails.UsernameNotFoundException; -
import org.springframework.stereotype.Service; -
import javax.annotation.Resource; -
/** -
* @author 小尘哥 -
*/ -
@Service("userService") -
public class UserService implements IUserService { -
@Resource -
private ISysUserService sysUserService; -
@Override -
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { -
return sysUserService.getByUsername(username); -
} -
}
基本以上就可以搞定基础的无状态鉴权了,如果需要更深入的了解,比如权限自定义等,请关注我后面的文章.......
本文分享自微信公众号 - 陌与尘埃(grq100296)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。