spring boot 集成jwt,security进行安全控制

spring boot 集成jwt security

文章结构

开门见山 数据流程

开门见山

这一部分直接展示代码,及将哪些代码进行修改就可以直接移值到自己的项目进行安全验证

代码目录结构

Auth

AuthController LoginUser

JWT

JwtUtil

Security

AuthFilter SecurityConfig UserDetailsImpl UserDetailsServiceImpl

User

UserController UserService User UserRepository

AuthController

说明:该类是自定义的用户进行登录验证获取token 的接口,是所有人都能访问的

package demo.demo1.auth.api.rest;

import demo.demo1.User.model.User; import
demo.demo1.User.service.UserService; import
demo.demo1.auth.jwt.JwtUtil; import demo.demo1.auth.model.LoginUser;
import io.swagger.annotations.Api; import
io.swagger.annotations.ApiOperation; import
io.swagger.annotations.ApiParam; import
org.springframework.beans.factory.annotation.Autowired; import
org.springframework.security.authentication.AuthenticationManager;
import
org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.validation.annotation.Validated; import
org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;

@RestController @RequestMapping("/auth") @CrossOrigin(origins = “*”)
@Api(
value = “/auth”,
description = “用户登录认证” ) public class AuthController {

@Autowired
private UserService userService;

@Autowired
private JwtUtil jwtUtil;

@Autowired
private AuthenticationManager authenticationManager;

@RequestMapping(value = "", method = RequestMethod.POST)
@ApiOperation(
        value = "登录",
        produces = "application/json"
)
public void login(
        @ApiParam(value = "登录用户名/密码", name = "LoginUser", required = true)
        @Validated
        @RequestBody LoginUser loginUser,
        HttpServletResponse response) throws Exception {

    try {
        /** 通过security验证登录账号是否正确,这里直接将用户和密码传入security就好,
         * 不需要在这里进行验证,你的验证会在userDetailService中由security帮你进行
         */
        authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                        loginUser.getUsername(),
                        loginUser.getPassword()
                )
        );
    } catch (AuthenticationException e) {
        throw new Exception("Username or Password error.");
    }

    // 验证通过后返回一个token值在http head中
    User user = userService.getUserByUserName(loginUser.getUsername());
    String token = jwtUtil.generateToken(user);
    // set token to header
    response.setHeader(JwtUtil.HEADER_STRING, token);
} }

LoginUser

说明: 该类是你进行登录时的bean,这里主要就是为了和user进行区分,登录的时候用这个

bean package demo.demo1.auth.model;

import io.swagger.annotations.ApiModel;

import javax.validation.constraints.NotNull;

@ApiModel(value = “Login User”, description = “登录用户信息”) public class
LoginUser {

@NotNull
private String username;

@NotNull
private String password;

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;
} }

JwtUtil

说明:这个类就是依据你的登录用户生成jwt token,验证/解析请求时的token

package demo.demo1.auth.jwt;

import demo.demo1.User.model.User; import
demo.demo1.User.service.UserService; import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts; import
io.jsonwebtoken.SignatureAlgorithm; import
org.springframework.beans.factory.annotation.Autowired; import
org.springframework.beans.factory.annotation.Value; import
org.springframework.stereotype.Component;

import java.util.Date; import java.util.HashMap; import java.util.Map;
import java.util.UUID;

@Component public class JwtUtil {

/**
token前缀
 */
public static final String TOKEN_PREFIX = "Bearer ";

/**
 * 设置http head中Authorization字段为token
 */
public static final String HEADER_STRING = "Authorization";

@Value("${jwt.secret}")
private String secret;

@Value("${jwt.expiration}")
private Long expiration;

@Autowired
private UserService userService;

/**
 * 依据登录的账号生成token
 */
public String generateToken(User user) throws Exception {

    if (user == null || user.getId() == null) {
        throw new Exception(String.format("user %s not valid", user));
    }

    //设置token参数
    Map<String, Object> claims = new HashMap<>();
    claims.put("sub", user.getId());
    claims.put("aud", "web");
    claims.put("iss", "demo");
    claims.put("iat", new Date());

    return JwtUtil.TOKEN_PREFIX + Jwts.builder()
            .setClaims(claims)
            .setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
            .signWith(SignatureAlgorithm.HS512, secret)
            .compact();

}

/**
 * 解析token
 */
public Claims parseTokenClaims(String token) throws Exception {

    try {
        String pure = token.replace(JwtUtil.TOKEN_PREFIX, "");
        return Jwts.parser().setSigningKey(secret).parseClaimsJws(pure).getBody();
    } catch (Exception e) {
        throw new Exception(e.getMessage());
    }

}

/**
 * 验证token
 */
public Boolean validateToken(String token) {
    
    try {
        String pure = token.replace(JwtUtil.TOKEN_PREFIX, "");
        Claims claims = parseTokenClaims(pure);
        String subject = claims.getSubject();
        User user = userService.getUserById(UUID.fromString(subject));
        if (user == null) {
            return false;
        } else if (claims.getExpiration().after(new Date())) {
            return true;
        }
        return false;
    } catch (Exception e) {

    }
    return false;
}
 }

AuthFilter

说明

这个类继承了OncePerRequestFilter

当发送一个携带token的http请求访问某个接口的时候,这个过滤器就进行验证其用户权限

该类重写了doFilterInternal方法,在该方法中通过token进行权限验证

package demo.demo1.auth.security;

import demo.demo1.User.service.UserService; import
demo.demo1.auth.jwt.JwtUtil; import org.slf4j.Logger; import
org.slf4j.LoggerFactory; import
org.springframework.beans.factory.annotation.Autowired; import
org.springframework.core.env.Environment; import
org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import
org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import
org.springframework.security.core.userdetails.UserDetailsService;
import
org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component; import
org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain; import
javax.servlet.ServletException; import
javax.servlet.http.HttpServletRequest; import
javax.servlet.http.HttpServletResponse; import java.io.IOException;
import java.util.UUID;

@Component public class AuthFilter extends OncePerRequestFilter {

private static final Logger logger = LoggerFactory.getLogger(AuthFilter.class);

@Autowired
private JwtUtil jwtUtil;

@Autowired
private UserDetailsService userDetailsService;

@Autowired
private UserService userService;

@Autowired
private Environment env;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
        throws IOException, ServletException {

    String token = request.getHeader(JwtUtil.HEADER_STRING);
    if (token != null && token.startsWith(JwtUtil.TOKEN_PREFIX)) {
        token = token.replace(JwtUtil.TOKEN_PREFIX, "");
        try {
            String id = jwtUtil.parseTokenClaims(token).getSubject();
            String username = userService.getUserById(UUID.fromString(id)).getUsername();
            if (null != id && SecurityContextHolder.getContext().getAuthentication() == null) {
                logger.debug("Checking token for user {}", id);
                // In security, use uuid as username.
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                if (jwtUtil.validateToken(token) && userDetails != null) {
                    // create authentication
                    UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                            userDetails, null, userDetails.getAuthorities()
                    );
                    // set authentication
                    authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    // put authentication into context holder
                    SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                }
            }
        } catch (Exception e) {
            logger.debug("Check token failed {}", e.getMessage());
        }
    }

    chain.doFilter(request, response);

}

SecurityConfig

说明:该类是security的配置类,通过该类可以控制资源访问权限,通过什么方式进行验证用户权限

package demo.demo1.auth.security;

import org.springframework.beans.factory.annotation.Autowired; import
org.springframework.context.annotation.Bean; import
org.springframework.context.annotation.Configuration; import
org.springframework.security.authentication.AuthenticationManager;
import
org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import
org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import
org.springframework.security.config.annotation.web.builders.HttpSecurity;
import
org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import
org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import
org.springframework.security.core.userdetails.UserDetailsService;
import
org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration @EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) public class
SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private UserDetailsService userDetailsService;

@Bean
public AuthFilter authorizationFilterBean() throws Exception {
    return new AuthFilter();
}

@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            // 禁用csrf
            .csrf().disable()
            // 因为是用的jwt所以不需要session                       .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeRequests()
            // 运行auth路径访问
            .antMatchers("/auth").permitAll()
            // 设置允许访问的资源
            .antMatchers("/webjars/**").permitAll()
            .antMatchers(
                    "/swagger-resources/configuration/ui",
                    "/swagger-resources",
                    "/swagger-resources/configuration/security",
                    "/swagger-ui.html",
                    "/swagger-ui.html",
                    "/v2/*",
                    "/user"
            ).permitAll()
            .anyRequest().authenticated();

    // 设置security过滤器
    http
            .addFilterBefore(authorizationFilterBean(), UsernamePasswordAuthenticationFilter.class);

    http.headers().cacheControl();
}

/**
 * 设置用户权限验证方式
 */
@Override
protected void configure(AuthenticationManagerBuilder amb) throws Exception {
    amb.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}

// 装载BCrypt密码编码器
@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

}

UserDetailsImpl

说明:该类实现了security的UserDetails,进行自定义验证用户验证用户

值得注意的是,在转换的时候user role前缀必须为ROLE_,否则security会返回403状态码(我在这炸了一天)

package demo.demo1.auth.security;

import demo.demo1.User.model.User; import
org.springframework.security.core.GrantedAuthority; import
org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList; import java.util.Collection; import
java.util.List; import java.util.stream.Collectors;

public class UserDetailsImpl implements UserDetails {

private User user;

public UserDetailsImpl(User user) {
    this.user = user;
}

//将你自定义的用户角色转换为security的user role
//值得注意的是,在转换的时候user role前缀必须为ROLE_,否则security会返回403状态码
private static List<GrantedAuthority> mapToGrantedAuthorities(List<String> roles) {

    return roles.stream()
            .map(role -> new SimpleGrantedAuthority(role))
            .collect(Collectors.toList());

}

@Override
public boolean isAccountNonLocked() {
    return true;
}


@Override
public Collection<GrantedAuthority> getAuthorities() {
    List<String> roles = new ArrayList<String>() {{ add(user.getRole());}};
    if (roles == null) {
        roles = new ArrayList<String>();
    }
    return mapToGrantedAuthorities(roles);
}

//必须要有,可以自定义,判断用户是否被禁用
@Override
public boolean isEnabled() { return true; };

@Override
public String getPassword() {
    return user.getPassword();
}

@Override
public boolean isAccountNonExpired() {
    return true;
}

@Override
public boolean isCredentialsNonExpired() {
    return true;
}

@Override
public String getUsername() {
    return user.getUsername();
} }

UserDetailsServiceImpl

说明:该类继承了UserDetailsService,自定义security用户验证,只需要实现loadUserByUsername方法

该方法正常写法就如下所示,一般不需要修改

package demo.demo1.auth.security;

import demo.demo1.User.model.User; import
demo.demo1.User.service.UserService; import org.slf4j.Logger; import
org.slf4j.LoggerFactory; import
org.springframework.beans.factory.annotation.Autowired; import
org.springframework.security.core.userdetails.UserDetails; import
org.springframework.security.core.userdetails.UserDetailsService;
import
org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service public class UserDetailsServiceImpl implements
UserDetailsService {

private final static Logger logger = LoggerFactory.getLogger(UserDetailsServiceImpl.class);

@Autowired
private UserService userService;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
    try {
        User user = userService.getUserByUserName(username);
        if (user != null) {
            return new UserDetailsImpl(user);
        } else {
            throw new UsernameNotFoundException("username not found.");
        }
    } catch (UsernameNotFoundException e) {
        throw e;
    } catch (Exception e) {
        logger.error(e.getMessage());
        throw new UsernameNotFoundException(e.getMessage());
    }
    
} }

UserController

说明:用来验证security的一个例子

使用@PreAuthorize(“hasRole(‘ROLE_SENIOR’)”)注解限制只能ROLE_SENIOR权限的用户访问该接口

这里post接口没做限制方便实验的时候可以自定义用户

package demo.demo1.User.controller;

import demo.demo1.User.model.User; import
demo.demo1.User.service.UserService; import
demo.demo1.auth.security.SecurityConfig; import
io.swagger.annotations.; import
org.springframework.beans.factory.annotation.Autowired; import
org.springframework.security.access.prepost.PreAuthorize; import
org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.
;

import java.util.List; import java.util.UUID;

@RestController @RequestMapping("/user") @Api(
value = “/user”,
description = “用户API” ) public class UserController {

@Autowired
UserService userService;

@Autowired
SecurityConfig securityConfig;

@RequestMapping(value = "" , method = RequestMethod.GET)
@PreAuthorize("hasRole('ROLE_SENIOR')")
@ApiOperation(
        value = "get all User",
        code = 201,
        consumes = "application/json",
        produces = "application/json"
)
public List<User> getAllUser() {
    return userService.getAllUser();
}

@RequestMapping(value = "/{id}" , method = RequestMethod.GET)
@PreAuthorize("hasRole('ROLE_SENIOR')")
@ApiOperation(
        value = "get one user by user id",
        code = 201,
        consumes = "application/json",
        produces = "application/json"
)
public User getOneUser(
        @ApiParam(value = "用户UUID") @PathVariable UUID id
) {
    return userService.getUserById(id);
}

@RequestMapping(value = "" , method = RequestMethod.POST)
//@PreAuthorize("hasRole('ROLE_SENIOR')")
@ApiOperation(
        value = "create user",
        code = 201,
        consumes = "application/json",
        produces = "application/json"
)
public void create(
        @RequestBody User user
) throws Exception{

    user.setUsername(user.getUsername().trim());
    user.setPassword(user.getPassword().trim());
    user.setId(UUID.randomUUID());
    user.setEmail(user.getEmail().trim());

    // encode password
    BCryptPasswordEncoder passwordEncoder = (BCryptPasswordEncoder) securityConfig.passwordEncoder();
    user.setPassword(passwordEncoder.encode(user.getPassword()));

    userService.create(user);
}

}

UserService

说明:user的service层,不需要多说

package demo.demo1.User.service;

import demo.demo1.User.model.User; import
demo.demo1.User.model.UserRole; import
demo.demo1.User.repository.UserRepository; import
org.springframework.beans.factory.annotation.Autowired; import
org.springframework.stereotype.Service;

import java.util.List; import java.util.UUID;

@Service public class UserService {

@Autowired
private UserRepository userRepository;

public User create(User user) throws Exception{

    //check that the username already exists
    if(user.getUsername() == null) {
        throw new Exception("User name can not null");
    }
    if(userRepository.findByUsername(user.getUsername()) != null) {
        throw new Exception(String.format("User %S alrady exist" , user.getUsername()));
    }

    //check userRole
    if(user.getRole() != null) {
        if (!user.getRole().equals(UserRole.ROLE_LOWER.getValue()) && !user.getRole().equals(UserRole.ROLE_SENIOR.getValue()) &&

!user.getRole().equals(UserRole.ROLE_INTERMEDIATE.getValue())) {
throw new Exception(String.format(“User Role %s is invalid”, user.getRole()));
}
} else {
throw new Exception(“User role can not null”);
}

    return userRepository.save(user);
}

public User getUserById(UUID id) {

    return userRepository.findById(id).get();

}

public User getUserByUserName(String name) {

    return userRepository.findByUsername(name);

}

public List<User> getAllUser() {

    return userRepository.findAll();

}

}

User

说明:user bean

这里的set,get可用@Date注解,但是这里我用的builder模式,在写代码的时候service层会报红,没有安全感,所以都写上了

package demo.demo1.User.model;

import com.fasterxml.jackson.annotation.JsonProperty; import
io.swagger.annotations.ApiModelProperty; import
org.hibernate.validator.constraints.NotBlank;

import javax.persistence.*; import javax.persistence.Column; import
javax.validation.constraints.NotNull; import
javax.validation.constraints.Size; import java.util.Set; import
java.util.UUID;

@Entity @Table(name = “DEMO_USER” , indexes = {
@Index(name = “IDX_USER” , columnList = “ID,USERNAME”) }) public class User {

public User(){};

public User(Builder builder) {
    setId(builder.id);
    setUsername(builder.username);
    setPassword(builder.password);
    setRole(builder.role);
    setEmail(builder.email);
}

@Id
@Column(name = "ID")
@org.hibernate.annotations.Type(type = "org.hibernate.type.PostgresUUIDType")
@ApiModelProperty(value = "用户ID", required = false, example = "876C2203-7472-44E8-9EB6-13CF372D326C")
private UUID id;

@Column(name = "USERNAME" , length = 60)
@NotBlank(message = "error.not_blank")
@Size(min = 1 , max = 50 , message = "error.size")
@ApiModelProperty(value = "用户名,长度1~50", required = true, example = "username")
private String username;

@Column(name = "PASSWORD", length = 60)
@NotBlank( message = "error.not_blank")
@Size(min = 1, max = 60 , message = "error.size")
@ApiModelProperty(value = "密码,长度1~25", required = true, example = "r00tme")
private String password;

@Column(name = "ROLE" , length = 20)
@NotNull
private String role;

@Column(name = "EMAIL" , length = 60)
@Size(min = 1, max = 320, message = "error.size")
@ApiModelProperty(value = "邮箱,长度1~60", required = true, example = "[email protected]")
private String email;


public UUID getId() {
    return id;
}

public void setId(UUID id) {
    this.id = id;
}

public String getUsername() {
    return username;
}

public void setUsername(String username) {
    this.username = username;
}

public String getPassword() {
    return password;
}

public String getEmail() {
    return email;
}

public void setEmail(String email) {
    this.email = email;
}

public static final class Builder {
    private UUID id;
    private String username;
    private String password;
     private String role;
    private String email;

    public Builder setId(UUID val) {
        this.id = val;
        return this;
    }

    public Builder setUsername(String val) {
        this.username = val;
        return this;
    }

    public Builder setPassword(String val) {
        this.password = val;
        return this;
    }

    public Builder setRole(String val) {
        this.role = val;
        return this;
    }


    public Builder setEmail(String val) {
        this.email = val;
        return this;
    }

    public User build() { return new User(this); }

}

}

UserRepository

package demo.demo1.User.repository;

import demo.demo1.User.model.User; import
demo.demo1.User.model.UserRole; import
org.springframework.data.jpa.repository.JpaRepository; import
org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;

import java.util.UUID;

@Repository public interface UserRepository extends
JpaRepository<User, UUID>, JpaSpecificationExecutor {

User findByUsername(String username);

}

数据流程

spring boot 集成jwt security认证大概流程(转自https://www.jianshu.com/p/ca4cebefd1cc)
在这里插入图片描述
首先是左边一张图,通过登陆接口获取token,该接口是任何权限都能个访问的
在这里插入图片描述
然后是右边一张图,说的是如何通过携带token访问接口
在这里插入图片描述
代码github

https://github.com/wheijxiaotbai/HXB_Knowledge/tree/springboot_jwt_security_demo

如何运行

postgresql
在这里插入图片描述
通过开发工具打开gradle项目,点击运行即可

在浏览器访问127.0.0.1:8080
在这里插入图片描述
在demo中没有对创建用户的接口做权限限制,方便自定义用户进行测试

猜你喜欢

转载自blog.csdn.net/zhuguang10/article/details/88635379