欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot整合SpringSecurity和JWT的示例

 更新時間:2024年10月11日 16:49:26   作者:今晚噠老虎  
這篇文章主要介紹了SpringBoot整合SpringSecurity和JWT的示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

JWT

1.介紹:

全稱 JSON Web Token,通過數(shù)字簽名的方式,以JSON為載體,在不同的服務終端之間安全的傳遞信息。

常用于授權認證,用戶登錄后的每個請求都包含JWT,后端處理請求之前都要進行校驗。

2.組成:

Header:數(shù)據(jù)頭,令牌類型和加密算法

Payload:負載,請求體和其他數(shù)據(jù)

Signature:簽名,把頭部的base64UrlEncode與負載的base64UrlEncode拼接起來再進行HMACSHA256加密

用戶認證流程

1.用戶提交登錄表單(用戶名和密碼)

2.后端校驗成功后生成JWT,通過response的header返回給前端

3.前端將JWT保存到LocalStorage

4.之后所有的請求中請求頭都攜帶JWT進行身份認證

在這里插入圖片描述

Spring Security(安全框架)

1、介紹
Spring Security是一個能夠為基于Spring的企業(yè)應用系統(tǒng)提供聲明式的安全訪問控制解決方案的安全框架。

如果項目中需要進行權限管理,具有多個角色和多種權限,我們可以使用Spring Security。

采用的是責任鏈的設計模式,是一堆過濾器鏈的組合,它有一條很長的過濾器鏈。

2、功能
Authentication (認證),就是用戶登錄
Authorization (授權),判斷用戶擁有什么權限,可以訪問什么資源
安全防護,跨站腳本攻擊,session攻擊等
非常容易結合Spring進行使用

3、Spring SecurityShiro的區(qū)別

優(yōu)點:

1、Spring Security基于Spring開發(fā),項目如果使用Spring作為基礎,配合Spring Security做權限更加方便。而Shiro需要和Spring進行整合開發(fā)
2、Spring Security功能比Shiro更加豐富,例如安全防護方面
3、Spring Security社區(qū)資源相對比Shiro更加豐富

缺點:

1)Shiro的配置和使用比較簡單,Spring Security上手復雜些
2)Shiro依賴性低,不需要依賴任何框架和容器,可以獨立運行。Spring Security依賴Spring容器

需要實現(xiàn)的過濾器和處理器

1、LogoutSuccessHandler:
  表示登出處理器
2、驗證碼過濾器Filter
3、登錄認證成功、失敗處理器
4、BasicAuthenticationFilter:
  該過濾器用于普通http請求進行身份認證
5、AuthenticationEntryPoint:
  表示認證失敗處理器
6、AccessDenieHandler:
  用戶發(fā)起無權限訪問請求的處理器
7、UserServiceDatils 接口:
  該接口十分重要,用于從數(shù)據(jù)庫中驗證用戶名密碼
8、PasswordEncoder密碼驗證器

整合

1.添加相應依賴

    <!-- springboot security -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!-- jwt -->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.1</version>
    </dependency>
    <dependency>
        <groupId>com.github.axet</groupId>
        <artifactId>kaptcha</artifactId>
        <version>0.0.9</version>
    </dependency>
    <!-- hutool工具類-->
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.3.3</version>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.11</version>
    </dependency>

    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
    </dependency>
    <dependency>
        <groupId>commons-codec</groupId>
        <artifactId>commons-codec</artifactId>
        <version>1.15</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>

2.寫一個JWT工具類(生成JWT、解析JWT、判斷JWT是否過期)

import io.jsonwebtoken.*;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Date;

@Data
@Component
@ConfigurationProperties(prefix = "hang.jwt")
public class JwtUtils {

    //使用@ConfigurationProperties注解可以讀取配置文件中的信息,只要在 Bean 上添加上了這個注解,指定好配置文件中的前綴,那么對應的配置文件數(shù)據(jù)就會自動填充到 Bean 的屬性中
    private long expire;
    private String secret;
    private String header;

    // 生成JWT
    public String generateToken(String username) {

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

        return Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setSubject(username)
                .setIssuedAt(nowDate)
                .setExpiration(expireDate)    // 7天過期
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    // 解析JWT
    public Claims getClaimsByToken(String jwt) {
        try {
            return Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(jwt)
                    .getBody();
        } catch (Exception e) {
            return null;
        }
    }

    // 判斷JWT是否過期
    public boolean isTokenExpired(Claims claims) {
        return claims.getExpiration().before(new Date());
    }

}
#JWT配置
hang:
  jwt:
    header: Authorization
    expire: 604800 # 7天,s為單位
    secret: abcdefghabcdefghabcdefghabcdefgh

封裝Result

import lombok.Data;

import java.io.Serializable;

@Data
public class Result implements Serializable {

    private int code;
    private String msg;
    private Object data;

    public static Result succ(Object data) {
        return succ(200, "操作成功", data);
    }

    public static Result fail(String msg) {
        return fail(400, msg, null);
    }

    public static Result succ (int code, String msg, Object data) {
        Result result = new Result();
        result.setCode(code);
        result.setMsg(msg);
        result.setData(data);
        return result;
    }

    public static Result fail (int code, String msg, Object data) {
        Result result = new Result();
        result.setCode(code);
        result.setMsg(msg);
        result.setData(data);
        return result;
    }
}

LoginSuccessHandler(登錄成功處理器)實現(xiàn)AuthenticationSuccessHandler

@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {

    @Autowired
    JwtUtils jwtUtils;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        ServletOutputStream outputStream = httpServletResponse.getOutputStream();

        // 生成JWT,并放置到請求頭中
        String jwt = jwtUtils.generateToken(authentication.getName());
        httpServletResponse.setHeader(jwtUtils.getHeader(), jwt);

        Result result = Result.succ("SuccessLogin");

        outputStream.write(JSONUtil.toJsonStr(result).getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        outputStream.close();
    }
}

LoginFailureHandler(登錄失敗處理器)實現(xiàn)AuthenticationFailureHandler

@Component
public class LoginFailureHandler implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        ServletOutputStream outputStream = httpServletResponse.getOutputStream();

        String errorMessage = "用戶名或密碼錯誤";
        Result result;
        if (e instanceof CaptchaException) {
            errorMessage = "驗證碼錯誤";
            result = Result.fail(errorMessage);
        } else {
            result = Result.fail(errorMessage);
        }
        outputStream.write(JSONUtil.toJsonStr(result).getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        outputStream.close();
    }
}

自定義的驗證碼異常

public class CaptchaException extends AuthenticationException {

    public CaptchaException(String msg) {
        super(msg);
    }
}

驗證碼工具類

@Configuration
public class KaptchaConfig {

    @Bean
    DefaultKaptcha producer() {
        Properties properties = new Properties();
        properties.put("kaptcha.border", "no");
        properties.put("kaptcha.textproducer.font.color", "black");
        properties.put("kaptcha.textproducer.char.space", "4");
        properties.put("kaptcha.image.height", "40");
        properties.put("kaptcha.image.width", "120");
        properties.put("kaptcha.textproducer.font.size", "30");
        Config config = new Config(properties);
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }

}

驗證碼 Controller

	@Autowired
    Producer producer;

	@GetMapping("/captcha")
    public Result Captcha() throws IOException {
        String key = UUID.randomUUID().toString();
        String code = producer.createText();

        BufferedImage image = producer.createImage(code);
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ImageIO.write(image, "jpg", outputStream);

        BASE64Encoder encoder = new BASE64Encoder();
        String str = "data:image/jpeg;base64,";

        String base64Img = str + encoder.encode(outputStream.toByteArray());

        //隨機碼為key,驗證碼為value
        redisUtil.hset(Const.CAPTCHA_KEY, key, code, 120);

        return Result.succ(
                MapUtil.builder()
                .put("userKey", key)
                .put("captcherImg", base64Img)
                .build()
        );
    }

驗證碼過濾器CaptchaFilter

@Component
public class CaptchaFilter extends OncePerRequestFilter {

    @Autowired
    RedisUtil redisUtil;

    @Autowired
    LoginFailureHandler loginFailureHandler;

    //自定義處理邏輯
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {

        String url = httpServletRequest.getRequestURI();
        if ("/login".equals(url) && httpServletRequest.getMethod().equals("POST")) {
            // 校驗驗證碼
            try {
                validate(httpServletRequest);
            } catch (CaptchaException e) {

                // 交給認證失敗處理器
                loginFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, e);
            }
        }
        filterChain.doFilter(httpServletRequest, httpServletResponse);
    }

    // 校驗驗證碼邏輯
    private void validate(HttpServletRequest httpServletRequest) {
        String code = httpServletRequest.getParameter("code");
        String key = httpServletRequest.getParameter("userKey");

        if (StringUtils.isBlank(code) || StringUtils.isBlank(key)) {
            throw new CaptchaException("驗證碼錯誤");
        }

        if (!code.equals(redisUtil.hget(Const.CAPTCHA_KEY, key))) {
            throw new CaptchaException("驗證碼錯誤");
        }

        // 若驗證碼正確,執(zhí)行以下語句
        // 一次性使用
        redisUtil.hdel(Const.CAPTCHA_KEY, key);
    }
}

JWT過濾器JwtAuthenticationFilter

檢驗JWT是否正確以及是否過期

public class JwtAuthenticationFilter extends BasicAuthenticationFilter {

    @Autowired
    JwtUtils jwtUtils;

    @Autowired
    UserDetailServiceImpl userDetailService;

    @Autowired
    SysUserService sysUserService;

    public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String jwt = request.getHeader(jwtUtils.getHeader());
        // 這里如果沒有jwt,繼續(xù)往后走,因為后面還有鑒權管理器等去判斷是否擁有身份憑證,所以是可以放行的
        // 沒有jwt相當于匿名訪問,若有一些接口是需要權限的,則不能訪問這些接口
        if (StrUtil.isBlankOrUndefined(jwt)) {
            chain.doFilter(request, response);
            return;
        }

        Claims claim = jwtUtils.getClaimsByToken(jwt);
        if (claim == null) {
            throw new JwtException("token 異常");
        }
        if (jwtUtils.isTokenExpired(claim)) {
            throw new JwtException("token 已過期");
        }

        String username = claim.getSubject();
        // 獲取用戶的權限等信息

        SysUser sysUser = sysUserService.getByUsername(username);


        // 構建UsernamePasswordAuthenticationToken,這里密碼為null,是因為提供了正確的JWT,實現(xiàn)自動登錄
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, null, userDetailService.getUserAuthority(sysUser.getId()));
        SecurityContextHolder.getContext().setAuthentication(token);

        chain.doFilter(request, response);

    }
}

SecurityContextHolder.getContext().getAuthentication().getPrincipal()等方法獲取到當前登錄的用戶信息

JWT認證失敗處理器JwtAuthenticationEntryPoint

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {


    //認證失敗的處理
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {

        httpServletResponse.setContentType("application/json;charset=UTF-8");
        httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        ServletOutputStream outputStream = httpServletResponse.getOutputStream();

        Result result = Result.fail("請先登錄");

        outputStream.write(JSONUtil.toJsonStr(result).getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        outputStream.close();
    }
}

從數(shù)據(jù)庫中驗證用戶名、密碼:UserServiceDetails、AuthenticationManager、UserDetails

SpringSecurity中的認證管理器AuthenticationManager是一個抽象接口,用以提供各種認證方式。一般我們都使用從數(shù)據(jù)庫中驗證用戶名、密碼是否正確這種認證方式。

AuthenticationManager的默認實現(xiàn)類是ProviderManager,ProviderManager提供很多認證方式,DaoAuthenticationProvider是AuthenticationProvider的一種實現(xiàn),可以通過實現(xiàn)UserDetailsService接口的方式來實現(xiàn)數(shù)據(jù)庫查詢方式登錄。

Spring Security在拿到UserDetails之后,會去對比Authentication(Authentication如何得到?我們使用的是默認的UsernamePasswordAuthenticationFilter,它會讀取表單中的用戶信息并生成Authentication),若密碼正確,則Spring Secuity自動幫忙完成登錄

在這里插入圖片描述

定義一個UserDetails接口的實現(xiàn)類,稱為AccountUser實現(xiàn)所有方法

public interface UserDetails extends Serializable {
    //獲取用戶權限
    Collection<? extends GrantedAuthority> getAuthorities();

    //用戶密碼
    String getPassword();

    //用戶名
    String getUsername();

    //用戶是否過期
    boolean isAccountNonExpired();

    //用戶是否被鎖定
    boolean isAccountNonLocked();

    //認證信息是否過期
    boolean isCredentialsNonExpired();

    //用戶啟用還是禁用
    boolean isEnabled();
}

實現(xiàn) UserDetails (默認有權限管理功能)

public class AccountUser implements UserDetails {

    private Long userId;

    private static final long serialVersionUID = 540L;
    private static final Log logger = LogFactory.getLog(User.class);
    private String password;
    private final String username;
    private final Collection<? extends GrantedAuthority> authorities;
    private final boolean accountNonExpired;
    private final boolean accountNonLocked;
    private final boolean credentialsNonExpired;
    private final boolean enabled;

    public AccountUser(Long userId, String username, String password, Collection<? extends GrantedAuthority> authorities) {
        this(userId, username, password, true, true, true, true, authorities);
    }

    public AccountUser(Long userId, String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
        Assert.isTrue(username != null && !"".equals(username) && password != null, "Cannot pass null or empty values to constructor");
        this.userId = userId;
        this.username = username;
        this.password = password;
        this.enabled = enabled;
        this.accountNonExpired = accountNonExpired;
        this.credentialsNonExpired = credentialsNonExpired;
        this.accountNonLocked = accountNonLocked;
        this.authorities = authorities;
    }

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

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

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

    @Override
    public boolean isAccountNonExpired() {
        return this.accountNonExpired;
    }

    @Override
    public boolean isAccountNonLocked() {
        return this.accountNonLocked;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return this.credentialsNonExpired;
    }

    @Override
    public boolean isEnabled() {
        return this.enabled;
    }
}

實現(xiàn) UserDetailsService 重寫其loadUserByUsername方法 使用用戶名在數(shù)據(jù)庫中查找用戶信息返回

@Service
public class UserDetailServiceImpl implements UserDetailsService {

    @Autowired
    SysUserService sysUserService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        SysUser sysUser = sysUserService.getByUsername(username);
        if (sysUser == null) {
            throw new UsernameNotFoundException("用戶名或密碼錯誤");
        }


        return new AccountUser(sysUser.getId(), sysUser.getUsername(), sysUser.getPassword(), getUserAuthority(sysUser.getId()));

    }

    /**
     * 獲取用戶權限信息(角色、菜單權限)
     * @param userId
     * @return
     */
    public List<GrantedAuthority> getUserAuthority(Long userId) {
    	// 實際怎么寫以數(shù)據(jù)表結構為準,這里只是寫個例子
        // 角色(比如ROLE_admin),菜單操作權限(比如sys:user:list)
        String authority = sysUserService.getUserAuthorityInfo(userId);     // 比如ROLE_admin,ROLE_normal,sys:user:list,...

        return AuthorityUtils.commaSeparatedStringToAuthorityList(authority);
    }
}

實現(xiàn)了上述幾個接口,從數(shù)據(jù)庫中驗證用戶名、密碼的過程將由框架幫我們完成,封裝隱藏了

無權限訪問的處理:AccessDenieHandler

當權限不足時,我們需要設置權限不足狀態(tài)碼403,并將錯誤信息返回給前端

@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
        ServletOutputStream outputStream = httpServletResponse.getOutputStream();

        Result result = Result.fail(e.getMessage());

        outputStream.write(JSONUtil.toJsonStr(result).getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        outputStream.close();
    }
}

登出處理器 LogoutSuccessHandler

1.將原來的 JWT 置為空返給前端

2.用空字符串覆蓋之前的 JWT (JWT是無狀態(tài) 無法銷毀 只能等過期 所以采用置空瀏覽器中保存的JWT)

3.清除SecurityContext中的用戶信息 (通過創(chuàng)建SecurityContextLogoutHandler對象,調用它的logout方法)

實現(xiàn) LogoutSuccessHandler 重寫 onLogoutSuccess 方法

@Component
public class JWTLogoutSuccessHandler implements LogoutSuccessHandler {

    @Autowired
    JwtUtils jwtUtils;

    @Override
    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {

        if (authentication != null) {
            new SecurityContextLogoutHandler().logout(httpServletRequest, httpServletResponse, authentication);
        }

        httpServletResponse.setContentType("application/json;charset=UTF-8");
        ServletOutputStream outputStream = httpServletResponse.getOutputStream();

        httpServletResponse.setHeader(jwtUtils.getHeader(), "");

        Result result = Result.succ("SuccessLogout");

        outputStream.write(JSONUtil.toJsonStr(result).getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        outputStream.close();
    }
}

密碼加密解密:PasswordEncoder

1.首先前端對密碼進行esa加密

2.后端對前端傳輸過來的密碼進行解密

3.再根據(jù)數(shù)據(jù)庫的加密規(guī)則BCrypt進行加密

SpringSecurity提供了用于密碼加密解密的工具類BCryptPasswordEncoder

需自定義PasswordEncoder類,并使其繼承BCryptPasswordEncoder,重寫其matches方法

@NoArgsConstructor
public class PasswordEncoder extends BCryptPasswordEncoder {

    //判斷從前端接收的密碼與數(shù)據(jù)庫中的密碼是否一致
    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        // 接收到的前端的密碼
        String pwd = rawPassword.toString();
        // 進行rsa解密
        try {
            pwd = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey, pwd);
        } catch (Exception e) {
            throw new BadCredentialsException(e.getMessage());
        }
        if (encodedPassword != null && encodedPassword.length() != 0) {
            return BCrypt.checkpw(pwd, encodedPassword);
        } else {
            return false;
        }
    }
}

Spring Security全局配置:SecurityConfig

需要繼承WebSecurityConfigurerAdapter(采用適配器模式,繼承后SecurityConfig可以看做是WebSecurityConfigurer)

SecurityConfig需要使用@EnableGlobalMethodSecurity(prePostEnabled = true)注解

Spring Security默認是禁用注解的,要想開啟注解,需要在繼承WebSecurityConfigurerAdapter的類上加@EnableGlobalMethodSecurity注解,來判斷用戶對某個控制層的方法是否具有訪問權限。prePostEnabled = true即可在方法前后進行權限檢查

Security內(nèi)置的權限注解如下:
  @PreAuthorize:方法執(zhí)行前進行權限檢查 @PreAuthorize("hasAuthority('sys:user:list')")  @PostAuthorize:方法執(zhí)行后進行權限檢查
  @Secured:類似于 @PreAuthorize
  可以在Controller的方法前添加這些注解表示接口需要什么權限。

配置類還需使用@EnableWebSecurity注解,該注解有兩個作用:1. 加載了WebSecurityConfiguration配置類, 配置安全認證策略。2.加載了AuthenticationConfiguration, 配置了認證信息。AuthenticationConfiguration這個類的作用就是用來創(chuàng)建ProviderManager。
  @EnableWebSecurity完成的工作便是加載了WebSecurityConfiguration,AuthenticationConfiguration這兩個核心配置類,也就此將spring security的職責劃分為了配置安全信息,配置認證信息兩部分。

在SecurityConfig這個配置類中,我們需要將之前寫的攔截器和處理器都autowire進來,并使用@Bean注解,聲明JwtAuthenticationFilter和PasswordEncoder的構造函數(shù)。在JwtAuthenticationFilter的構造函數(shù)中,我們調用authenticationManager()方法給JwtAuthenticationFilter提供AuthenticationManager。

配置類需要重寫configure方法進行配置,該方法有多種重載形式,我們使用其中的兩種,其中一個用于配置url安全攔截配置,另一個用于AuthenticationManager配置UserDetailsService的實現(xiàn)類

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

    @Autowired
    LoginFailureHandler loginFailureHandler;

    @Autowired
    LoginSuccessHandler loginSuccessHandler;

    @Autowired
    CaptchaFilter captchaFilter;

    @Autowired
    JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

    @Autowired
    JwtAccessDeniedHandler jwtAccessDeniedHandler;

    @Autowired
    UserDetailServiceImpl userDetailService;

    @Autowired
    JWTLogoutSuccessHandler jwtLogoutSuccessHandler;

    @Bean
    JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
        JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager());
        return jwtAuthenticationFilter;
    }


    private static final String[] URL_WHITELIST = {
      "/login",
      "/logout",
      "/captcha",
      "/favicon.ico"
    };

    @Bean
    PasswordEncoder PasswordEncoder() {
        return new PasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable()
                // 登錄配置
                .formLogin()
                .successHandler(loginSuccessHandler)
                .failureHandler(loginFailureHandler)

                .and()
                .logout()
                .logoutSuccessHandler(jwtLogoutSuccessHandler)

                // 禁用session
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                // 配置攔截規(guī)則
                .and()
                .authorizeRequests()
                .antMatchers(URL_WHITELIST).permitAll()
                .anyRequest().authenticated()
                // 異常處理器
                .and()
                .exceptionHandling()
                .authenticationEntryPoint(jwtAuthenticationEntryPoint)
                .accessDeniedHandler(jwtAccessDeniedHandler)

                // 配置自定義的過濾器
                .and()
                .addFilter(jwtAuthenticationFilter())
                // 驗證碼過濾器放在UsernamePassword過濾器之前
                .addFilterBefore(captchaFilter, UsernamePasswordAuthenticationFilter.class)
                ;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailService);
    }
}

自定義權限校驗注解

Spring Security提供了Spring EL表達式,允許我們在定義接口訪問的方法上面添加注解,來控制訪問權限。

@PreAuthorize 注解用于配置接口要求用戶擁有某些權限才可訪問

方法參數(shù)描述
hasPermiString驗證用戶是否具備某權限
lacksPermiString驗證用戶是否不具備某權限,與 hasPermi邏輯相反
hasAnyPermiString驗證用戶是否具有以下任意一個權限
hasRoleString判斷用戶是否擁有某個角色
lacksRoleString驗證用戶是否不具備某角色,與 isRole邏輯相反
hasAnyRolesString驗證用戶是否具有以下任意一個角色,多個逗號分隔

使用 @ss 代表 PermissionService(許可服務) 類,對每個接口攔截并調用PermissionService的對應方法判斷接口調用者的權限。

package com.example.framework.web.service;

import java.util.Set;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import com.example.common.core.domain.entity.SysRole;
import com.example.common.core.domain.model.LoginUser;
import com.example.common.utils.SecurityUtils;
import com.example.common.utils.StringUtils;
import com.example.framework.security.context.PermissionContextHolder;

/**
 * 自定義權限實現(xiàn),ss => SpringSecurity首字母
 * 超級管理員擁有所有權限,不受權限約束。
 */
@Service("ss")
public class PermissionService
{
    /** 所有權限標識 */
    private static final String ALL_PERMISSION = "*:*:*";

    /** 管理員角色權限標識 */
    private static final String SUPER_ADMIN = "admin";

    private static final String ROLE_DELIMETER = ",";

    private static final String PERMISSION_DELIMETER = ",";

    /**
     * 驗證用戶是否具備某權限
     * 
     * @param permission 權限字符串
     * @return 用戶是否具備某權限
     */
    public boolean hasPermi(String permission)
    {
        if (StringUtils.isEmpty(permission))
        {
            return false;
        }
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions()))
        {
            return false;
        }
        PermissionContextHolder.setContext(permission);
        return hasPermissions(loginUser.getPermissions(), permission);
    }

    /**
     * 驗證用戶是否不具備某權限,與 hasPermi邏輯相反
     *
     * @param permission 權限字符串
     * @return 用戶是否不具備某權限
     */
    public boolean lacksPermi(String permission)
    {
        return hasPermi(permission) != true;
    }

    /**
     * 驗證用戶是否具有以下任意一個權限
     *
     * @param permissions 以 PERMISSION_NAMES_DELIMETER 為分隔符的權限列表
     * @return 用戶是否具有以下任意一個權限
     */
    public boolean hasAnyPermi(String permissions)
    {
        if (StringUtils.isEmpty(permissions))
        {
            return false;
        }
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions()))
        {
            return false;
        }
        PermissionContextHolder.setContext(permissions);
        Set<String> authorities = loginUser.getPermissions();
        for (String permission : permissions.split(PERMISSION_DELIMETER))
        {
            if (permission != null && hasPermissions(authorities, permission))
            {
                return true;
            }
        }
        return false;
    }

    /**
     * 判斷用戶是否擁有某個角色
     * 
     * @param role 角色字符串
     * @return 用戶是否具備某角色
     */
    public boolean hasRole(String role)
    {
        if (StringUtils.isEmpty(role))
        {
            return false;
        }
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles()))
        {
            return false;
        }
        for (SysRole sysRole : loginUser.getUser().getRoles())
        {
            String roleKey = sysRole.getRoleKey();
            if (SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role)))
            {
                return true;
            }
        }
        return false;
    }

    /**
     * 驗證用戶是否不具備某角色,與 isRole邏輯相反。
     *
     * @param role 角色名稱
     * @return 用戶是否不具備某角色
     */
    public boolean lacksRole(String role)
    {
        return hasRole(role) != true;
    }

    /**
     * 驗證用戶是否具有以下任意一個角色
     *
     * @param roles 以 ROLE_NAMES_DELIMETER 為分隔符的角色列表
     * @return 用戶是否具有以下任意一個角色
     */
    public boolean hasAnyRoles(String roles)
    {
        if (StringUtils.isEmpty(roles))
        {
            return false;
        }
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles()))
        {
            return false;
        }
        for (String role : roles.split(ROLE_DELIMETER))
        {
            if (hasRole(role))
            {
                return true;
            }
        }
        return false;
    }

    /**
     * 判斷是否包含權限
     * 
     * @param permissions 權限列表
     * @param permission 權限字符串
     * @return 用戶是否具備某權限
     */
    private boolean hasPermissions(Set<String> permissions, String permission)
    {
        return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));
    }
}

數(shù)據(jù)權限示例

// 符合system:user:list權限要求
@PreAuthorize("@ss.hasPermi('system:user:list')")

// 不符合system:user:list權限要求
@PreAuthorize("@ss.lacksPermi('system:user:list')")

// 符合system:user:add或system:user:edit權限要求即可
@PreAuthorize("@ss.hasAnyPermi('system:user:add,system:user:edit')")

角色權限示例

// 屬于user角色
@PreAuthorize("@ss.hasRole('user')")

// 不屬于user角色
@PreAuthorize("@ss.lacksRole('user')")

// 屬于user或者admin之一
@PreAuthorize("@ss.hasAnyRoles('user,admin')")

公開接口(不需要驗證權限可以公開訪問的)

使用注解方式,只需要在Controller的類或方法上加入@Anonymous該注解即可

// @PreAuthorize("@ss.xxxx('....')") 注釋或刪除掉原有的權限注解
@Anonymous
@GetMapping("/list")
public List<SysXxxx> list(SysXxxx xxxx)
{
    return xxxxList;
}

前端

前端需要做兩件事,一是登錄成功后把JWT存到localStore里面,二是在每次請求之前,都在請求頭中添加JWT
我們在store文件夾里創(chuàng)建index.js,將JWT定義為token,以及定義SET_TOKEN方法

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    token: ''
  },
  mutations: {
    SET_TOKEN: (state, token) => {
      state.token = token
      localStorage.setItem("token", token)
    },


  },
  actions: {
  },
  modules: {
    
  }
})

在登錄成功時,接收后端傳來的JWT并保存

const jwt = res.headers['authorization']
this.$store.commit('SET_TOKEN', jwt)

在src文件夾下創(chuàng)建axios.js,進行axios配置,配置前置攔截器,為所有需要權限的請求裝配上header的token信息

const request = axios.create({
    timeout: 5000,
    headers: {
        'Content-Type': "application/json; charset=utf-8"
    }
})

// 前置攔截,為所有需要權限的請求裝配上header的token信息
request.interceptors.request.use(config => {
    config.headers['Authorization'] = localStorage.getItem("token")
    return config
})

 到此這篇關于SpringBoot整合SpringSecurity和JWT的示例的文章就介紹到這了,更多相關Springboot SpringSecurity JWT內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Java深入探索單例模式的應用

    Java深入探索單例模式的應用

    單例模式(Singleton Pattern)是 Java 中最簡單的設計模式之一。這種類型的設計模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的最佳方式
    2022-06-06
  • Spring中的Eureka服務過期詳細解析

    Spring中的Eureka服務過期詳細解析

    這篇文章主要介紹了Spring中的Eureka服務過期詳細解析,如果有一些服務過期了,或者宕機了,就不會調用shutdown()方法,也不會去發(fā)送請求下線服務實例,需要的朋友可以參考下
    2023-11-11
  • 基于Integer值判斷是否相等的問題

    基于Integer值判斷是否相等的問題

    這篇文章主要介紹了基于Integer值判斷是否相等的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-01-01
  • SpringBoot入門之集成Druid的方法示例

    SpringBoot入門之集成Druid的方法示例

    這篇文章主要介紹了SpringBoot入門之集成Druid的方法示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-07-07
  • springboot?+rabbitmq+redis實現(xiàn)秒殺示例

    springboot?+rabbitmq+redis實現(xiàn)秒殺示例

    本文主要介紹了springboot?+rabbitmq+redis實現(xiàn)秒殺示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-07-07
  • Java、JavaScript、Oracle、MySQL中實現(xiàn)的MD5加密算法分享

    Java、JavaScript、Oracle、MySQL中實現(xiàn)的MD5加密算法分享

    這篇文章主要介紹了Java、JavaScript、Oracle、MySQL中實現(xiàn)的MD5加密算法分享,需要的朋友可以參考下
    2014-09-09
  • SpringSecurity注銷設置的方法

    SpringSecurity注銷設置的方法

    這篇文章主要為大家詳細介紹了SpringSecurity注銷設置的方法,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-09-09
  • java中char類型轉換成int類型的2種方法

    java中char類型轉換成int類型的2種方法

    這篇文章主要給大家介紹了關于java中char類型轉換成int類型的2種方法,因為java是一門強類型語言,所以在數(shù)據(jù)運算中會存在類型轉換,需要的朋友可以參考下
    2023-07-07
  • 以用戶名注冊為例分析三種Action獲取數(shù)據(jù)的方式

    以用戶名注冊為例分析三種Action獲取數(shù)據(jù)的方式

    這篇文章主要介紹了以用戶名注冊為例分析三種Action獲取數(shù)據(jù)的方式的相關資料,需要的朋友可以參考下
    2016-03-03
  • Java swing五子棋的實現(xiàn)方法

    Java swing五子棋的實現(xiàn)方法

    這篇文章主要為大家詳細介紹了Java swing五子棋的實現(xiàn)方法,Java開發(fā)圖形界面程序五子棋的實現(xiàn)方式,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-06-06

最新評論