SpringBoot + SpringSecurity + Jwt快速使用

前后端分離情況。

最重要的兩步就是自定義攔截器和定義SecurityConfig配置類(lèi)。

新建SpringBoot項(xiàng)目
Maven引入依賴(lài):

<!-- JWT -->
        <dependency>
          <groupId>io.jsonwebtoken</groupId>
          <artifactId>jjwt</artifactId>
          <version>0.9.1</version>
        </dependency>

                <!--    MYSQL        -->
                <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!--    MyBatis-Plus     -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>

        <!--   FastJson      -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.83</version>
        </dependency>

        <!--    Reids        -->
<!--        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency> -->

SecurityCofig

1、SecurityConfig中定義了哪些接口可以被開(kāi)放
最少需要開(kāi)放登錄接口、注銷(xiāo)接口、【注冊(cè)】
簡(jiǎn)單設(shè)置如下:

A1、關(guān)閉csrf、關(guān)閉session
A2、開(kāi)放白名單
A3、將自定義注解放到默認(rèn)的UsernamePasswordAuthenticationFilter前。
@Resource 注入的是:自定義過(guò)濾器、登錄失敗處理器、權(quán)限異常處理器
除@Configuration另外兩個(gè)注解主要是開(kāi)啟Security注解鑒權(quán)、啟用Web安全

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

    @Resource
    JwtFilter jwtFilter;

    @Resource
    NotLoginHandler notLoginHandler;

    @Resource
    AuthorizationErrorHandler authorizationErrorHandler;


    @Override
    protected void configure(HttpSecurity http) throws Exception {

        //1、關(guān)閉csrf,關(guān)閉Session
        http
                .csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        //2、設(shè)置不需要認(rèn)證的URL
        http
                .authorizeRequests()
                //允許未登錄的用戶(hù)進(jìn)行訪問(wèn)
                .antMatchers("/user/**").anonymous()
                //其余url都要認(rèn)證才能訪問(wèn)
                .anyRequest().authenticated();

        http.exceptionHandling() //異常處理
                .authenticationEntryPoint(notLoginHandler)
                .accessDeniedHandler(authorizationErrorHandler);


        http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
    }

自定義Jwt過(guò)濾器

在我的理解中Jwt存儲(chǔ)了主要有三個(gè)部分:過(guò)期時(shí)間、用戶(hù)非重要信息、用戶(hù)權(quán)限信息[可]
【或者用token+redis的方式】
在由前端請(qǐng)求時(shí)Header中攜帶這個(gè)token。

主要步驟為:對(duì)請(qǐng)求進(jìn)行攔截 &【解析Jwt、生成Security令牌】、放行

這里和不分離項(xiàng)目不同的是,是繼承這個(gè)OncePreRequestFIlter。
而不是Security內(nèi)置的AbstractAuthenticationProcessingFilter


@Component
public class JwtFilter extends OncePerRequestFilter {

    @Resource
    JwtUtils jwtUtils;

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

        // 1、獲取前端Header-Token
        String header = request.getHeader(SecurityConstant.TOKEN_NAME);
        if (header != null && StringUtils.isNoneBlank(header)){
            // 2、解析token

            Claims claimByToken = jwtUtils.getClaimByToken(header);

            if (!jwtUtils.isTokenExpired(claimByToken)) {
                String id = claimByToken.getId();
                String subject = claimByToken.getSubject();
                String rolesJson = (String) claimByToken.get(SecurityConstant.JWT_AUTHORITIES);
                Set<String> roles = JSONObject.parseObject(rolesJson, Set.class);
                Set<GrantedAuthority> authorities = new HashSet<>();
                for (String role :
                        roles) {
                    authorities.add(new SimpleGrantedAuthority(role));
                }
                // 3、在SecurityHolder設(shè)置Authentication
                SecurityContextHolder.getContext().setAuthentication(new JwtAuthenticationToken(id, subject, authorities));
            }
        }
        chain.doFilter(request, response);
    }
}

ELSE

到這里我認(rèn)為SpringSecurity的基本骨架已經(jīng)搭建好了。
SpringSecurity還需要添加的代碼:
1、Security的AuthenticationToken——讓Security認(rèn)可你登錄的令牌
2、登錄失敗【未登錄】處理器
3、權(quán)限校驗(yàn)異常處理器

不重要:自定義登錄接口、JwtUtils,一些小方法代碼
見(jiàn)文末其他代碼。

其他代碼

Security的AuthenticationToken

/**
* jwt身份驗(yàn)證令牌
*
*/

public class JwtAuthenticationToken extends AbstractAuthenticationToken {

    private String username;
    private String userId;

    public JwtAuthenticationToken(String userId, String username, Collection<? extends GrantedAuthority> authorities ){
        super(authorities);
        this.userId = userId;
        this.username = username;
        super.setAuthenticated(true);
    }

    public String getUsername() {
        return username;
    }

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

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    @Override
    public Object getCredentials() {
        return null;
    }

    @Override
    public Object getPrincipal() {
        return this.username;
    }
}

登錄失敗[未登錄](méi)處理器

/**
 * 未登錄處理程序
 */
@Component
public class NotLoginHandler implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpStatus.FORBIDDEN.value());
        ResBean notLogin = ResBean.NOT_LOGIN;
        JsonResult<Object> result = new JsonResult<>();
        result.setMessage(notLogin.getMessage());
        result.setSuccess(false);
        result.setTotal(0);
        result.setStatus(notLogin.getCode());
        result.setBody(notLogin);
        response.getWriter().write(JSONObject.toJSONString(result));
    }
}

權(quán)限校驗(yàn)異常處理器

@Component
public class AuthorizationErrorHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpStatus.FORBIDDEN.value());
        ResBean notLogin = ResBean.NO_ROLES;
        JsonResult<Object> result = new JsonResult<>();
        result.setMessage(notLogin.getMessage());
        result.setSuccess(false);
        result.setTotal(0);
        result.setStatus(notLogin.getCode());
        result.setBody(notLogin);
        response.getWriter().write(JSONObject.toJSONString(result));
    }
}

JwtUtils

@Data
    @Component
    public class JwtUtils {

        private long expire;
        private String secret;
        private String header;


        /**
        * 生成令牌
        *
        * @param user  用戶(hù)
        * @param roles 角色
        * @return {@link String}
        */
        public String generateToken(SysUser user, Set<String> roles) {

            Date nowDate = new Date();
            Date expireDate = new Date(nowDate.getTime() + 1000 * SecurityConstant.EXPIRE_TIME);

            return Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setId(String.valueOf(user.getId()))
                .setSubject(user.getNickname())
                .claim(SecurityConstant.JWT_AUTHORITIES, JSONObject.toJSONString(roles))
                .setIssuedAt(nowDate)
                .setExpiration(expireDate)
                .signWith(SignatureAlgorithm.HS512, SecurityConstant.JWT_SIGN)
                .compact();
        }

        /**
        * 解析
        *
        * @param jwt jwt
        * @return {@link Claims}
        */
        public Claims getClaimByToken(String jwt) {
            try {
                return Jwts.parser()
                    .setSigningKey(SecurityConstant.JWT_SIGN)
                    .parseClaimsJws(jwt)
                    .getBody();
            } catch (Exception e) {
                return null;
            }
        }

        /**
* 是令牌過(guò)期
*
* @param claims 
* @return boolean
*/
        public boolean isTokenExpired(Claims claims) {
            return claims.getExpiration().before(new Date());
        }

    }

登錄部分截圖

image.png

RespBean為公共對(duì)象返回枚舉

不展示了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容