SpringBoot项目搭建 + Jwt登录

临时接了一个小项目,有需要搭一个小项目,简单记录一下项目搭建过程以及整合登录功能。

1.首先拿到的是一个码云地址,里面是一个空的文件夹,只有一个

2. 拿到HTTPS码云项目地址链接,在IDEA中clone输入项目地址

3. 在项目根目录文件夹下右击,选择Add Frameworks Support, 添加maven框架支持

4. 然后得到pom.xml文件, 在pom文件中配置springboot

点击查看代码
    <groupId>groupId</groupId>
    <artifactId>resource-manager-background</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.3.12.RELEASE</version>
    </parent>

5. 引入其他相关依赖 mysql druid mybatisplus

点击查看代码
<dependencies>
	<!-- mysqlconnect -->
	<dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
		<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.1</version>
        </dependency>
	<!-- Mybatis-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.0</version>
        </dependency>
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.12</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.4.0</version>
        </dependency>
	<!-- jwt -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
        </dependency>
	<!-- security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
	<!-- springweb -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
</dependencies>

<properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <swagger2.version>2.9.2</swagger2.version>
</properties>

6. 创建security文件夹,创建Security核心配置类

点击查看代码
package com.gameresource.security.config;

import com.gameresource.base.bean.Resp;
import com.gameresource.base.constant.RespConstant;
import com.gameresource.security.filter.JwtAuthTokenFilter;
import com.gameresource.security.util.RespUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;


public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private IgnoreUrlsConfig ignoreUrlsConfig;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.authorizeRequests();
        for (String url : ignoreUrlsConfig.getUrls()) {
            registry.antMatchers(url).permitAll();
        }
        registry.antMatchers(HttpMethod.OPTIONS).permitAll();
        registry.and()
                .authorizeRequests()
                .anyRequest()
                .authenticated()

                //关闭跨站请求防护及不使用防护
                .and()
                .csrf()
                .disable()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)

                //自定义权限拒绝处理类
                .and()
                .exceptionHandling()
                .accessDeniedHandler(((request, response, accessDeniedException) -> {
                    RespUtils.outJson(response, Resp.code(RespConstant.NOT_PERMISSION));
                }))
                .authenticationEntryPoint((request, response, authenticationException) -> {
                    RespUtils.outJson(response, Resp.code(RespConstant.NOT_AUTHORIZE));
                })

                //自定义权限拦截器JWT过滤器
                .and()
                .addFilterBefore(jwtAuthTokenFilter(), UsernamePasswordAuthenticationFilter.class);
        //关闭默认登录
        http.formLogin().disable().httpBasic().disable();
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers(
                "/auth/login",
                "/doc.html",
                "/swagger-ui.html",
                "/v2/api-docs",
                "/swagger-resources/**",
                "/**/*.js",
                "/**/*.css",
                "/**/*.png",
                "/**/*.ico"
        );
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public JwtAuthTokenFilter jwtAuthTokenFilter() {
        return new JwtAuthTokenFilter();
    }

}

7. 自定义JWT过滤器

点击查看代码
package com.gameresource.security.filter;

import cn.hutool.core.util.StrUtil;
import com.gameresource.base.bean.Resp;
import com.gameresource.base.constant.RespConstant;
import com.gameresource.security.constant.SecurityConstant;
import com.gameresource.security.config.IgnoreUrlsConfig;
import com.gameresource.security.util.JwtTokenUtil;
import com.gameresource.security.util.RespUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
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.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;

/**
 * 自定义JWT过滤器
 */
@Slf4j
public class JwtAuthTokenFilter extends OncePerRequestFilter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private IgnoreUrlsConfig ignoreUrlsConfig;
    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        String authHeader = request.getHeader(SecurityConstant.TOKEN_HEADER);
        String uri = request.getRequestURI();
        if (StrUtil.startWith(authHeader, SecurityConstant.TOKEN_HEAD)) {
            try {
                String token = StrUtil.trim(authHeader.substring(SecurityConstant.TOKEN_HEAD.length()));
                String username = jwtTokenUtil.getUserNameFromToken(token);

                log.info("checking username:{}", username);
                if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                    if (jwtTokenUtil.validateToken(token)) {
                        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                        authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                        log.info("pass user: {}", username);
                        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                    }
                }
            } catch (BadCredentialsException exception) {
                RespUtils.outJson(response, Resp.fail(exception.getMessage()));
                return;
            } catch (Exception exception) {
                log.error("Spring Security 校验异常: ", exception);
                RespUtils.outJson(response, Resp.code(RespConstant.NOT_AUTHORIZE));
                return;
            }
        }
        chain.doFilter(request, response);
    }


}

8. 创建JWT工具类

点击查看代码
package com.gameresource.security.util;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.gameresource.security.constant.SecurityConstant;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * jwt工具类
 */

@Slf4j
@Component
@RequiredArgsConstructor
public class JwtTokenUtil {

    @Value("${project.name}")
    private String projectName;
    @Value("${Jwt.secret}")
    private String secret;
    @Value("${Jwt.expiration}")
    private Integer expiration;


    private final StringRedisTemplate redisTemplate;

    public boolean validateToken(String token) {
        String username = getUserNameFromToken(token);

        String redisKey = TokenRedisKeyUtils.installAuthToken(projectName, username);
        if (Boolean.FALSE.equals(redisTemplate.hasKey(redisKey))) {
            throw new BadCredentialsException("Token不存在或已过期");
        }

        String redisToken = redisTemplate.opsForValue().get(redisKey);
        if (!StrUtil.equals(redisToken, token)) {
            throw new BadCredentialsException("无效token");
        }
        return true;
    }

    /**
     * 从token中获取用户名
     */
    public String getUserNameFromToken(String token) {
        return Convert.toStr(getValueFromToken(token, SecurityConstant.CLAIM_KEY_USERNAME));
    }

    private Object getValueFromToken(String token, String key) {
        Claims claim = getClaimsFromToken(token);
        return claim != null ? claim.get(key) : null;
    }

    private Claims getClaimsFromToken(String token) {
        Claims claims = null;
        try {
            claims = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception exception) {
            log.error("jwt格式验证失败: {}", token);
        }
        return claims;
    }

    public String generateToken(UserDetails userDetails) {
        return generateToken(userDetails, null);
    }

    private String generateToken(UserDetails userDetails, Map<String, Object> claims) {
        if (claims == null) {
            claims = new HashMap<>();
        }
        claims.put(SecurityConstant.CLAIM_KEY_USERNAME, userDetails.getUsername());

        int ttl = expiration * 1000;
        DateTime dateTime = DateUtil.offsetMillisecond(new Date(), ttl);
        String token = Jwts.builder()
                .setClaims(claims)
                .setExpiration(dateTime)
                .signWith(SignatureAlgorithm.HS256, secret)
                .compact();
        String redisKey = TokenRedisKeyUtils.installAuthToken(projectName, userDetails.getUsername());
        redisTemplate.opsForValue().set(redisKey, token, ttl, TimeUnit.MILLISECONDS);
        return token;
    }

    public Boolean removeToken(String username) {
        return redisTemplate.delete(TokenRedisKeyUtils.installAuthToken(projectName, username));
    }
}

9. 响应工具类

点击查看代码
package com.gameresource.security.util;

import com.alibaba.fastjson.JSON;
import com.gameresource.base.bean.Resp;
import org.springframework.http.MediaType;
import org.springframework.security.core.parameters.P;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class RespUtils {

    public static void outJson(HttpServletResponse response, Resp resp) {
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setStatus(HttpServletResponse.SC_OK);
        response.setCharacterEncoding("utf-8");

        try (PrintWriter printWriter = response.getWriter();) {
            printWriter.print(JSON.toJSONString(resp));
            printWriter.flush();
        }catch (IOException exception) {
            throw new RuntimeException();
        }

    }

}

10. UserDetailsService的自定义

点击查看代码
package com.gameresource.security.service;

import com.gameresource.manage.sys.SysUserManager;
import lombok.RequiredArgsConstructor;
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
@RequiredArgsConstructor
public class AuthUserDetailService implements UserDetailsService {

    private final SysUserManager userManager;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return userManager.findUserByName(username);
    }
}

11. 各种类的实现代码

1. SysUserManager

点击查看代码

package com.gameresource.manage.sys;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.gameresource.module.sys.domain.AuthUser;
import com.gameresource.module.sys.entity.SysUserEntity;
import com.gameresource.module.sys.entity.SysUserRoleEntity;
import com.gameresource.module.sys.service.ISysUserRoleService;
import com.gameresource.module.sys.service.ISysUserService;
import com.gameresource.security.constant.SecurityConstant;
import com.gameresource.security.util.JwtTokenUtil;
import com.sun.org.apache.xpath.internal.operations.Bool;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
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.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.stream.Collectors;

@RequiredArgsConstructor
@Component
@Slf4j
public class SysUserManager {

    private final ISysUserService userService;
    @Autowired
    private PasswordEncoder passwordEncoder;
    private final ISysUserRoleService userRoleService;
    private final JwtTokenUtil jwtTokenUtil;

    public String login(String username, String password) {
        UserDetails userDetails = findUserByName(username);
        if (passwordEncoder.matches(password, userDetails.getPassword())) {
            throw new BadCredentialsException("用户名或密码错误");
        }
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        return jwtTokenUtil.generateToken(userDetails);
    }

    public UserDetails findUserByName(String username) {
        SysUserEntity userEntity = userService.getByUsername(username);
        if (ObjectUtil.isNull(userEntity)) {
            throw new UsernameNotFoundException("用户不存在!");
        }
        if (!userEntity.getStatus()) {
            throw new RuntimeException("此账户已被禁用!");
        }

        List<SysUserRoleEntity> roleEntityList = userRoleService.getByUserId(userEntity.getId());
        List<Integer> roleList = roleEntityList.stream().map(SysUserRoleEntity::getRoleId).collect(Collectors.toList());
        return AuthUser.create(userEntity, roleList, null);
    }

    public boolean logout(HttpServletRequest request) {
        String token = request.getHeader(SecurityConstant.TOKEN_HEADER);
        if (StrUtil.isBlank(token)) {
            return false;
        }
        token = token.substring(SecurityConstant.TOKEN_HEAD.length());
        String username = jwtTokenUtil.getUserNameFromToken(token);
        Boolean logout = jwtTokenUtil.removeToken(username);
        if (!logout) {
            log.error("============登出失败============");
        }
        log.info("=======================用户退出{}======================", username);
        return true;
    }
}


2. AuthUser

点击查看代码
package com.gameresource.module.sys.domain;

import cn.hutool.core.collection.CollUtil;
import com.gameresource.module.sys.entity.SysUserEntity;
import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

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

@Data
@Accessors(chain = true)
public class AuthUser implements UserDetails {

    /**
     * 用户id
     */
    private int userId;

    /**
     * 用户名
     */
    private String username;

    /**
     * 密码
     */
    private String password;

    /**
     * 是否启用
     */
    private boolean status;

    /**
     * 用户角色列表
     */
    private List<Integer> roleIds;

    /**
     * 用户权限列表
     */
    private Collection<? extends GrantedAuthority> authorities;

    public static UserDetails create(SysUserEntity sysUser, List<Integer> roleIds, List<String> perms) {
        if (sysUser == null) {
            return null;
        }
        List<GrantedAuthority> authorities = Collections.emptyList();
        if (CollUtil.isNotEmpty(perms)) {
            authorities = perms.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
        }

        return new AuthUser()
                .setUserId(sysUser.getId())
                .setStatus(sysUser.getStatus())
                .setUsername(sysUser.getUsername())
                .setPassword(sysUser.getPassword())
                .setRoleIds(CollUtil.isNotEmpty(roleIds) ? roleIds : Collections.emptyList())
                .setAuthorities(authorities);
    }


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

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

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

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

    @Override
    public boolean isEnabled() {
        return status;
    }
}

12. 登录接口

点击查看代码
package com.gameresource.web.sys;

import cn.hutool.core.lang.Assert;
import com.gameresource.base.bean.Resp;
import com.gameresource.manage.sys.SysUserManager;
import com.gameresource.module.sys.domain.AuthUser;
import com.gameresource.module.sys.domain.AuthUserVo;
import com.gameresource.module.sys.domain.LoginReqDto;
import com.gameresource.util.SecurityUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;

@Slf4j
@Api(tags = "登录")
@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
public class LoginController {

    private final SysUserManager sysUserManager;

    @ApiOperation("用户登录")
    @PostMapping("/login")
    public Resp<AuthUserVo> login(@Valid @RequestBody LoginReqDto loginReq) {
        try {
            log.info("===============loginReq=====================username:[{}],password:[{}]",loginReq.getUsername(), loginReq.getPassword());
            String token = sysUserManager.login(loginReq.getUsername(), loginReq.getPassword());
            Assert.notBlank(token, "用户名或密码错误");
            AuthUser authUser = SecurityUtil.getCurrentUser();
            log.info("========================用户{}登录成功!=============================", authUser.getUsername());
            return Resp.ok(AuthUserVo.create(authUser, token));
        }catch (Exception e) {
            log.info("登录失败: ", e);
            return Resp.fail(e.getMessage());
        }
    }

    @ApiOperation("用户退出")
    @PostMapping("/logout")
    public Resp<Boolean> logout(HttpServletRequest request) {
        return Resp.result(sysUserManager.logout(request));
    }

}

13. 至此结束

其实登录的核心部分就是WebSecurityConfigurerAdapter下的configure方法,我们通过自定义了一个SecurityConfig类来继承了WebSecurityConfigurerAdapter类,并重写配置类的各种选项达到自定义的效果,另外一个就是OncePerRequestFilter来过滤各种请求,在这里我们会处理请求并验证token,这只是项目中比较简单的一种写法,另外security还有很多其他的配置,在这里只是列举了其中的一种较为简单的方式,才疏学浅,还需要多加学习,共勉!

posted @ 2022-08-18 23:17  mmpyeah  阅读(186)  评论(0)    收藏  举报
css 代码内容