Spring Security + JWT 入门实战

2022-08-13 10:05:33

Spring Security + JWT 入门实战

##主要步骤

  1. 搭建基础的springboot工程,导入相关依赖
  2. 配置mysql,引用jpa
  3. 开启JPA支持
  4. 创建User实体,及controller,service,repository相关类
  5. 创建Jwt工具类,用于管理token相关的操作
  6. 创建JwtUser类,主要用于封装登录用户相关信息,例如用户名,密码,权限集合等,必须实现UserDetails 接口
  7. 创建JwtUserService 必须实现UserDetailsService,重写loadUserByUsername()方法,这样我们可以查询自己的数据库是否存在当前登录的用户名
  8. 创建拦截器,主要用于拦截用户登录信息,验证的事交给spring-security自己去做,验证成功会返回一个token,失败返回错误信息即可
  9. 用户验证成功过后会拿到token,下面的请求就需要携带这个token,后台需要一个新的拦截器进行权限验证
  10. 两个拦截器有了之后,只需要一个SecurityConfig将他们串联起来就行了

1. 搭建基础的springboot工程,导入相关依赖,项目整体结构和pom.xml 文件如下

在这里插入图片描述


<properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--springSecurity跟jwt的依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.security.oauth</groupId><artifactId>spring-security-oauth2</artifactId><version>2.3.5.RELEASE</version></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency><!--添加jpa支持--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><!--mysql依赖包--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!--通过lombok包,实体类中不需要再写set,get方法,只需要添加一个@Data注解即可--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.12</version><scope>provided</scope></dependency></dependencies>

2.配置mysql,引用jpa,application.properties文件如下

#端口设置
server.port=8088

#数据库连接
spring.datasource.url=jdbc:mysql://localhost:3306/db01?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=UTC
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456

 jpa配置
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

3. 开启JPA支持,启动项上加@EnableJpaRepositories注解即可

@SpringBootApplication
@EnableJpaRepositories// 加一个这个注解即可开启JPA支持publicclassApplication{publicstaticvoidmain(String[] args){
      SpringApplication.run(Application.class, args);}}

4.新增一张User表,及controller,service,repository相关类

@Entity
@Data// 注入该注解可以免去写set get方法publicclassUser{

    @Id
    @GeneratedValueprivate Integer id;private String username;private String password;private String role;}
@RequestMapping("/user")
@RestControllerpublicclassUserController{

    @Autowired
    UserServiceInterface userServiceInterface;
    @Autowiredprivate BCryptPasswordEncoder bCryptPasswordEncoder;

    @PostMappingpublic Usersave(@RequestBody User parameter){
        User user=newUser();
        user.setUsername(parameter.getUsername());
        user.setPassword(bCryptPasswordEncoder.encode(parameter.getPassword()));if("admin".equals(parameter.getUsername())){
            user.setRole("ADMIN");}else{
            user.setRole("USER");}return userServiceInterface.save(user);}

    @GetMappingpublic UserfindByUsername(@RequestParam String username){return userServiceInterface.findByUsername(username);}

    @GetMapping("/findAll")
    @PreAuthorize("hasAnyAuthority('ADMIN')")//这一步很重要 拥有ADMIN权限的用户才能访问该请求public List<User>findAll(){return userServiceInterface.findAll();}}
/**
 *  一定要加上 @Service 注解
 */
@ServicepublicclassUserServiceimplementsUserServiceInterface{

    @Autowired
    UserRepository userRepository;

    @Overridepublic Usersave(User user){return userRepository.save(user);}

    @Overridepublic UserfindByUsername(String username){return userRepository.findByUsername(username);}

    @Overridepublic List<User>findAll(){return userRepository.findAll();}}
publicinterfaceUserServiceInterface{

    Usersave(User user);

    UserfindByUsername(String username);

    List<User>findAll();}
/**
 *   @Repository 必须加上
 *   必须继承  extends JpaRepository<User,Long>
 */
@RepositorypublicinterfaceUserRepositoryextendsJpaRepository<User,Long>{

    Usersave(User user);

    UserfindByUsername(String username);

    List<User>findAll();}

5.创建Jwt工具类,用于管理token相关的操作

import io.jsonwebtoken.Claims;import io.jsonwebtoken.Jwts;import io.jsonwebtoken.SignatureAlgorithm;import java.util.Date;import java.util.HashMap;import java.util.Map;/**
 * jwt 工具类 主要是生成token 检查token等相关方法
 */publicclassJwtUtils{publicstatic final StringTOKEN_HEADER="Authorization";publicstatic final StringTOKEN_PREFIX="Bearer ";// TOKEN 过期时间publicstatic final longEXPIRATION=1000*60*30;// 三十分钟publicstatic final StringAPP_SECRET_KEY="secret";privatestatic final StringROLE_CLAIMS="rol";/**
     * 生成token
     *
     * @param username
     * @param role
     * @return
     */publicstatic StringcreateToken(String username, String role){

        Map<String, Object> map=newHashMap<>();
        map.put(ROLE_CLAIMS, role);

        String token= Jwts.builder().setSubject(username).setClaims(map).claim("username", username).setIssuedAt(newDate()).setExpiration(newDate(System.currentTimeMillis()+EXPIRATION)).signWith(SignatureAlgorithm.HS256,APP_SECRET_KEY).compact();return token;}/**
     * 获取当前登录用户用户名
     *
     * @param token
     * @return
     */publicstatic StringgetUsername(String token){
        Claims claims= Jwts.parser().setSigningKey(APP_SECRET_KEY).parseClaimsJws(token).getBody();return claims.get("username").toString();}/**
     * 获取当前登录用户角色
     *
     * @param token
     * @return
     */publicstatic StringgetUserRole(String token){
        Claims claims= Jwts.parser().setSigningKey(APP_SECRET_KEY).parseClaimsJws(token).getBody();return claims.get("rol").toString();}/**
     * 获解析token中的信息
     *
     * @param token
     * @return
     */publicstatic ClaimscheckJWT(String token){try{
            final Claims claims= Jwts.parser().setSigningKey(APP_SECRET_KEY).parseClaimsJws(token).getBody();return claims;}catch(Exception e){
            e.printStackTrace();returnnull;}}/**
     * 检查token是否过期
     *
     * @param token
     * @return
     */publicstatic booleanisExpiration(String token){
        Claims claims= Jwts.parser().setSigningKey(APP_SECRET_KEY).parseClaimsJws(token).getBody();return claims.getExpiration().before(newDate());}}

6.创建JwtUser类,主要用于封装登录用户相关信息,例如用户名,密码,权限集合等,必须实现UserDetails接口

publicclassJwtUserimplementsUserDetails{private Integer id;private String username;private String password;private Collection<?extendsGrantedAuthority> authorities;publicJwtUser(){}// 写一个能直接使用user创建jwtUser的构造器publicJwtUser(User user){
        id= user.getId();
        username= user.getUsername();
        password= user.getPassword();
        authorities= Collections.singleton(newSimpleGrantedAuthority(user.getRole()));}public Collection<?extendsGrantedAuthority>getAuthorities(){return authorities;}public StringgetPassword(){return password;}public StringgetUsername(){return username;}public booleanisAccountNonExpired(){returntrue;}public booleanisAccountNonLocked(){returntrue;}public booleanisCredentialsNonExpired(){returntrue;}public booleanisEnabled(){returntrue;}}

7.创建JwtUserService 必须实现UserDetailsService,重写loadUserByUsername()方法

@ServicepublicclassJwtUserServiceimplementsUserDetailsService{

    @Autowired
    UserService userService;/**
     * 根据前端传入的用户信息 去数据库查询是否存在该用户
     * @param s
     * @return
     * @throws UsernameNotFoundException
     */
    @Overridepublic UserDetailsloadUserByUsername(String s) throws UsernameNotFoundException{
        User user=this.userService.findByUsername(s);if(user!=null){
            JwtUser jwtUser=newJwtUser(user);return jwtUser;}else{try{thrownewValidationException("该用户不存在");}catch(ValidationException e){
                e.printStackTrace();}}returnnull;}}

8.配置拦截器,主要用于拦截用户登录信息

/**
 * 验证用户名密码正确后,生成一个token,并将token返回给客户端
 * 该类继承自UsernamePasswordAuthenticationFilter,重写了其中的2个方法 ,
 * attemptAuthentication:接收并解析用户凭证。
 * successfulAuthentication:用户成功登录后,这个方法会被调用,我们在这个方法里生成token并返回。
 */publicclassJWTAuthenticationFilterextendsUsernamePasswordAuthenticationFilter{private AuthenticationManager authenticationManager;/**
     * security拦截默认是以POST形式走/login请求,我们这边设置为走/token请求
     * @param authenticationManager
     */publicJWTAuthenticationFilter(AuthenticationManager authenticationManager){this.authenticationManager= authenticationManager;super.setFilterProcessesUrl("/token");}/**
     * 接收并解析用户凭证
     * @param request
     * @param response
     * @return
     * @throws AuthenticationException
     */
    @Overridepublic AuthenticationattemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException{// 从输入流中获取到登录的信息try{
            User loginUser=newObjectMapper().readValue(request.getInputStream(), User.class);return authenticationManager.authenticate(newUsernamePasswordAuthenticationToken(loginUser.getUsername(), loginUser.getPassword()));}catch(IOException e){
            e.printStackTrace();returnnull;}}// 成功验证后调用的方法// 如果验证成功,就生成token并返回
    @OverrideprotectedvoidsuccessfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authResult) throws IOException, ServletException{

        JwtUser jwtUser=(JwtUser) authResult.getPrincipal();
        System.out.println("jwtUser:"+ jwtUser.toString());

        String role="";
        Collection<?extendsGrantedAuthority> authorities= jwtUser.getAuthorities();for(GrantedAuthority authority: authorities){
            role= authority.getAuthority();}

        String token= JwtUtils.createToken(jwtUser.getUsername(), role);// 返回创建成功的token  但是这里创建的token只是单纯的token// 按照jwt的规定,最后请求的时候应该是 `Bearer token`
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        String tokenStr= JwtUtils.TOKEN_PREFIX+ token;
        response.setHeader("token", tokenStr);}// 失败 返回错误就行
    @OverrideprotectedvoidunsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException{
        response.getWriter().write("authentication failed, reason: "+ failed.getMessage());}}

9.权限拦截器

假如admin登录成功后,携带token去请求其他接口时,该拦截器会判断权限是否正确

/**
 * 登录成功之后走此类进行  鉴定 权限
 */publicclassJWTAuthorizationFilterextendsBasicAuthenticationFilter{publicJWTAuthorizationFilter(AuthenticationManager authenticationManager){super(authenticationManager);}

    @SneakyThrows
    @OverrideprotectedvoiddoFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws IOException, ServletException{

        String tokenHeader= request.getHeader(JwtUtils.TOKEN_HEADER);// 如果请求头中没有Authorization信息则直接放行了if(tokenHeader==null||!tokenHeader.startsWith(JwtUtils.TOKEN_PREFIX)){
            chain.doFilter(request, response);return;}// 如果请求头中有token,则进行解析,并且设置认证信息
        SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader));super.doFilterInternal(request
  • 作者:爱撸铁的程序猿
  • 原文链接:https://blog.csdn.net/weixin_45452416/article/details/109528425
    更新时间:2022-08-13 10:05:33