1. 引入相关依赖
<!-- JJWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.6.0</version>
</dependency>
<!--security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2.实体类准备
-
SysUser
/** id */
@Id
private Integer id;
/** 密码 */
private String password;
/** 用户名 */
private String username;
/**权限列表**/
@Transient
private List<SysRole> roles;
-
SysRole
/** id */
@Id
private Integer id;
/** name */
private String name;
-
JwtUser
/**
* security需要的UserDetails实现类
*/
@Data
public class JwtUser implements UserDetails {
private static final long serialVersionUID = -4959252432107932674L;
private final long id;
private final String username;
private final String password;
/** 权限类.*/
private final Collection<? extends GrantedAuthority> authorities;
/**
* 在createJwtFactory里注入
*/
public JwtUser(long id,
String username,
String password,
Collection<? extends GrantedAuthority> authorities) {
this.id = id;
this.username = username;
this.password = password;
this.authorities = authorities;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
@JsonIgnore
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@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;
}
}
-
JwtUserFactory
private JwtUserFactory() {
}
/**
* 创建JwtUser工厂
*/
public static JwtUser create(SysUser user){
return new JwtUser(
user.getId(),
user.getUsername(),
user.getPassword(),
map2GrantedAuthorities(user.getRoles())
);
}
/**
* 讲User的List<Role>转换成JwtUser<GrantedAuthority>
*/
private static List<GrantedAuthority> map2GrantedAuthorities(List<SysRole> authorities){
return authorities.stream()
.map(e -> role2SimpleGrantedAuthority(e))
.collect(Collectors.toList());
}
private static SimpleGrantedAuthority role2SimpleGrantedAuthority(SysRole role){
return new SimpleGrantedAuthority(role.getName());
}
3.实现UserDetailService接口
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
SysUser sysUser = userRepository.findByUserName(s); //调用持久层从数据库获取用户信息
if (sysUser == null)
throw new UsernameNotFoundException("用户名不存在");
List<SysRole> roles = sysRoleRepository.findRolesByUserId(sysUser.getId()); //根据用户id或者用户权限列表
if (CollectionUtils.isEmpty(roles))
roles = Collections.emptyList();
sysUser.setRoles(roles);
return JwtUserFactory.create(sysUser);
}
4.由于前后端分离所以服务器端不能用session控制,这里需要在Security的过滤器开始之前,从header中取得jwt的token去jwt中进行校验,如果验证通过则在本次request中植入security需要的验证信息
- security的前置jwt过滤器
@Component
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
/**
* 这里注入的是前面写的UserDetailsService的实现类
*/
@Autowired
private UserDetailsService customUserService;
@Value("${jwt.header}")
private String tokenHeader;
@Value("${jwt.tokenHead}")
private String tokenHead;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain)
throws ServletException, IOException {
String authHeader = request.getHeader(this.tokenHeader); // 取得header
if (authHeader != null && authHeader.startsWith(tokenHead)) { //判断header头
final String authToken = authHeader.substring(tokenHead.length()); // The part after "Bearer "
if (JwtUtil.getClaim(authToken) != null) { //去jwt中获得验证token
String username = JwtUtil.getClaim(authToken).getSubject(); //从jwt中获取信息,如果要缓存很多信息可以用Claims
logger.info("checking authentication " + username);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.customUserService.loadUserByUsername(username); //验证jwt的信息是否正确
//将验证信息放入SecurityContextHolder中,UsernamePasswordAuthenticationToken是Security验证账号密码的工具类
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(
request));
logger.info("authenticated user " + username + ", setting security context");
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
chain.doFilter(request, response);
}
}
- 这是关键
//将验证信息放入SecurityContextHolder中,UsernamePasswordAuthenticationToken是Security验证账号密码的工具类
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(
request));
logger.info("authenticated user " + username + ", setting security context");
SecurityContextHolder.getContext().setAuthentication(authentication);
- jwt工具类
/**
* 根据token获取用户信息
*/
public static Claims getClaim(String token){
try {
Claims claims = Jwts.parser()
.setSigningKey("abcdefg")
.parseClaimsJws(token).getBody();
return claims;
}catch (Exception e){
return null;
}
}
/**
* 设置用户信息进jwt
*/
public static String setClaim(String subject){
String token = Jwts
.builder()
.setSubject(subject)
.setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS256, "abcdefg")
.compact();
return token;
}
5 security配置类
@Configuration
@EnableWebSecurity //开启WebSecurity支持
@EnableGlobalMethodSecurity(prePostEnabled = true) //开启prePostEnabled注解支持
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService jwtUserDetailsServiceImpl;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(jwtUserDetailsServiceImpl)
.passwordEncoder(passwordEncoder());
}
/**
* 密码加密的bean,使用BCrypt
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 前置过滤器
* @return
*/
@Bean
JwtAuthenticationTokenFilter authenticationTokenFilterBean(){
return new JwtAuthenticationTokenFilter();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable() //禁用csrf保护
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) //禁用session
.and()
.authorizeRequests() //所有请求都要验证
.antMatchers("/auth/**").permitAll() //登录注册等请求过滤
.antMatchers(
"/",
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.js",
"/**/*.css"
).permitAll() //静态资源过滤
.anyRequest().fullyAuthenticated()
.and()
.exceptionHandling() //验证不通过的配置
.authenticationEntryPoint(new RestAuthenticationEntryPoint())
;
http //添加前置过滤器
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
http //禁用header缓存
.headers().cacheControl();
}
}
6.RestAuthenticationEntryPoint
/**
* 实现AuthenticationEntryPoint的commence方法自定义校验不通过的方法
* Created by gaowenfeng on 2017/8/9.
*/
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
// 捕获AuthenticationException中的message,并封装成自定义异常抛出
response.setCharacterEncoding("utf-8");
response.getWriter().write( new Result().setCode(ResultCode.UNAUTHORIZED).setMessage(AuthErrorEnum.AUTH_HEADER_ERROR.getMessage()).toString());
}
}
7.登录service
@Override
public String login(String username, String password) {
UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(username, password);
final Authentication authentication = authenticationManager.authenticate(upToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
final UserDetails userDetails = userDetailsService.loadUserByUsername(username);
final String token = JwtUtil.setClaim(username);
return token;
}
8.登录controller
@RequestMapping(value = "/auth/login", method = RequestMethod.GET)
public ResponseEntity<?> refreshAndGetAuthenticationToken(
HttpServletRequest request){
String token = request.getHeader(tokenHeader);
String refreshedToken = authService.refresh(token);
if(refreshedToken == null) {
return ResponseEntity.badRequest().body(null);
} else {
return ResponseEntity.ok(refreshedToken);
}
}
9.最后附上github地址
https://github.com/MarkGao11520/spring-boot-security-restful
网友评论