真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

如何實(shí)現(xiàn)SpringBoot集成SpringSecurity和JWT做登陸鑒權(quán)

小編這次要給大家分享的是如何實(shí)現(xiàn)SpringBoot集成SpringSecurity和JWT做登陸鑒權(quán),文章內(nèi)容豐富,感興趣的小伙伴可以來(lái)了解一下,希望大家閱讀完這篇文章之后能夠有所收獲。

十載的保靖網(wǎng)站建設(shè)經(jīng)驗(yàn),針對(duì)設(shè)計(jì)、前端、開(kāi)發(fā)、售后、文案、推廣等六對(duì)一服務(wù),響應(yīng)快,48小時(shí)及時(shí)工作處理。全網(wǎng)營(yíng)銷推廣的優(yōu)勢(shì)是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動(dòng)調(diào)整保靖建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無(wú)論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計(jì),從而大程度地提升瀏覽體驗(yàn)。成都創(chuàng)新互聯(lián)從事“保靖網(wǎng)站設(shè)計(jì)”,“保靖網(wǎng)站推廣”以來(lái),每個(gè)客戶項(xiàng)目都認(rèn)真落實(shí)執(zhí)行。

廢話

目前流行的前后端分離讓Java程序員可以更加專注的做好后臺(tái)業(yè)務(wù)邏輯的功能實(shí)現(xiàn),提供如返回Json格式的數(shù)據(jù)接口就可以。SpringBoot的易用性和對(duì)其他框架的高度集成,用來(lái)快速開(kāi)發(fā)一個(gè)小型應(yīng)用是最佳的選擇。

一套前后端分離的后臺(tái)項(xiàng)目,剛開(kāi)始就要面對(duì)的就是登陸和授權(quán)的問(wèn)題。這里提供一套方案供大家參考。

主要看點(diǎn):

  • 登陸后獲取token,根據(jù)token來(lái)請(qǐng)求資源
  • 根據(jù)用戶角色來(lái)確定對(duì)資源的訪問(wèn)權(quán)限
  • 統(tǒng)一異常處理
  • 返回標(biāo)準(zhǔn)的Json格式數(shù)據(jù)

正文

首先是pom文件:


    
      org.springframework.boot
      spring-boot-starter
    
    
      org.projectlombok
      lombok
      true
    
    
    
      org.springframework.boot
      spring-boot-starter-data-solr
    
        
    
      org.springframework.boot
      spring-boot-starter-web
    
    
      org.mybatis.spring.boot
      mybatis-spring-boot-starter
      1.3.2
    
    
      MySQL
      mysql-connector-java
      runtime
    
    
      org.springframework.boot
      spring-boot-configuration-processor
      true
    
    
      io.springfox
      springfox-swagger2
      2.6.1
    
    
      io.springfox
      springfox-swagger-ui
      2.6.1
    
    
      org.springframework.boot
      spring-boot-starter-data-rest
    
    
      org.springframework.boot
      spring-boot-starter-security
    
    
      org.springframework.security
      spring-security-jwt
      1.0.9.RELEASE
    
    
      io.jsonwebtoken
      jjwt
      0.9.0
    
    
      org.springframework.boot
      spring-boot-starter-test
      test
    
  

application.yml:

spring :
 datasource :
  url : jdbc:mysql://127.0.0.1:3306/les_data_center?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useAffectedRows=true&useSSL=false
  username : root
  password : 123456
  driverClassName : com.mysql.jdbc.Driver
 jackson:
  data-format: yyyy-MM-dd HH:mm:ss
  time-zone: GMT+8
mybatis :
 config-location : classpath:/mybatis-config.xml
# JWT
jwt:
 header: Authorization
 secret: mySecret
 #token有效期一天
 expiration: 86400
 tokenHead: "Bearer "

接著是對(duì)security的配置,讓security來(lái)保護(hù)我們的API

SpringBoot推薦使用配置類來(lái)代替xml配置。那這里,我也使用配置類的方式。

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

  private final JwtAuthenticationEntryPoint unauthorizedHandler;

  private final AccessDeniedHandler accessDeniedHandler;

  private final UserDetailsService CustomUserDetailsService;

  private final JwtAuthenticationTokenFilter authenticationTokenFilter;

  @Autowired
  public WebSecurityConfig(JwtAuthenticationEntryPoint unauthorizedHandler,
               @Qualifier("RestAuthenticationAccessDeniedHandler") AccessDeniedHandler accessDeniedHandler,
               @Qualifier("CustomUserDetailsService") UserDetailsService CustomUserDetailsService,
               JwtAuthenticationTokenFilter authenticationTokenFilter) {
    this.unauthorizedHandler = unauthorizedHandler;
    this.accessDeniedHandler = accessDeniedHandler;
    this.CustomUserDetailsService = CustomUserDetailsService;
    this.authenticationTokenFilter = authenticationTokenFilter;
  }

  @Autowired
  public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
    authenticationManagerBuilder
        // 設(shè)置UserDetailsService
        .userDetailsService(this.CustomUserDetailsService)
        // 使用BCrypt進(jìn)行密碼的hash
        .passwordEncoder(passwordEncoder());
  }
  // 裝載BCrypt密碼編碼器
  @Bean
  public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }

  @Override
  protected void configure(HttpSecurity httpSecurity) throws Exception {
    httpSecurity
        .exceptionHandling().accessDeniedHandler(accessDeniedHandler).and()
        // 由于使用的是JWT,我們這里不需要csrf
        .csrf().disable()
        .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
        // 基于token,所以不需要session
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()

        .authorizeRequests()

        // 對(duì)于獲取token的rest api要允許匿名訪問(wèn)
        .antMatchers("/api/v1/auth", "/api/v1/signout", "/error/**", "/api/**").permitAll()
        // 除上面外的所有請(qǐng)求全部需要鑒權(quán)認(rèn)證
        .anyRequest().authenticated();

    // 禁用緩存
    httpSecurity.headers().cacheControl();

    // 添加JWT filter
    httpSecurity
        .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
  }

  @Override
  public void configure(WebSecurity web) throws Exception {
    web.ignoring().antMatchers("/v2/api-docs",
        "/swagger-resources/configuration/ui",
        "/swagger-resources",
        "/swagger-resources/configuration/security",
        "/swagger-ui.html"
    );
  }

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

該類中配置了幾個(gè)bean來(lái)供security使用。

  1. JwtAuthenticationTokenFilter:token過(guò)濾器來(lái)驗(yàn)證token有效性
  2. UserDetailsService:實(shí)現(xiàn)了DetailsService接口,用來(lái)做登陸驗(yàn)證
  3. JwtAuthenticationEntryPoint :認(rèn)證失敗處理類
  4. RestAuthenticationAccessDeniedHandler: 權(quán)限不足處理類

那么,接下來(lái)一個(gè)一個(gè)實(shí)現(xiàn)這些類:

/**
 * token校驗(yàn),引用的stackoverflow一個(gè)答案里的處理方式
 * Author: JoeTao
 * createAt: 2018/9/14
 */
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

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

  @Resource
  private JWTUtils jwtUtils;

  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
    String auth_token = request.getHeader(this.token_header);
    final String auth_token_start = "Bearer ";
    if (StringUtils.isNotEmpty(auth_token) && auth_token.startsWith(auth_token_start)) {
      auth_token = auth_token.substring(auth_token_start.length());
    } else {
      // 不按規(guī)范,不允許通過(guò)驗(yàn)證
      auth_token = null;
    }

    String username = jwtUtils.getUsernameFromToken(auth_token);

    logger.info(String.format("Checking authentication for user %s.", username));

    if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
      User user = jwtUtils.getUserFromToken(auth_token);
      if (jwtUtils.validateToken(auth_token, user)) {
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        logger.info(String.format("Authenticated user %s, setting security context", username));
        SecurityContextHolder.getContext().setAuthentication(authentication);
      }
    }
    chain.doFilter(request, response);
  }
}
/**
 * 認(rèn)證失敗處理類,返回401
 * Author: JoeTao
 * createAt: 2018/9/20
 */
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {

  private static final long serialVersionUID = -8970718410437077606L;

  @Override
  public void commence(HttpServletRequest request,
             HttpServletResponse response,
             AuthenticationException authException) throws IOException {
    //驗(yàn)證為未登陸狀態(tài)會(huì)進(jìn)入此方法,認(rèn)證錯(cuò)誤
    System.out.println("認(rèn)證失?。? + authException.getMessage());
    response.setStatus(200);
    response.setCharacterEncoding("UTF-8");
    response.setContentType("application/json; charset=utf-8");
    PrintWriter printWriter = response.getWriter();
    String body = ResultJson.failure(ResultCode.UNAUTHORIZED, authException.getMessage()).toString();
    printWriter.write(body);
    printWriter.flush();
  }
}

因?yàn)槲覀兪褂玫腞EST API,所以我們認(rèn)為到達(dá)后臺(tái)的請(qǐng)求都是正常的,所以返回的HTTP狀態(tài)碼都是200,用接口返回的code來(lái)確定請(qǐng)求是否正常。

/**
* 權(quán)限不足處理類,返回403
 * Author: JoeTao
 * createAt: 2018/9/21
 */
@Component("RestAuthenticationAccessDeniedHandler")
public class RestAuthenticationAccessDeniedHandler implements AccessDeniedHandler {
  @Override
  public void handle(HttpServletRequest httpServletRequest, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
    //登陸狀態(tài)下,權(quán)限不足執(zhí)行該方法
    System.out.println("權(quán)限不足:" + e.getMessage());
    response.setStatus(200);
    response.setCharacterEncoding("UTF-8");
    response.setContentType("application/json; charset=utf-8");
    PrintWriter printWriter = response.getWriter();
    String body = ResultJson.failure(ResultCode.FORBIDDEN, e.getMessage()).toString();
    printWriter.write(body);
    printWriter.flush();
  }
}
/**
 * 登陸身份認(rèn)證
 * Author: JoeTao
 * createAt: 2018/9/14
 */
@Component(value="CustomUserDetailsService")
public class CustomUserDetailsService implements UserDetailsService {
  private final AuthMapper authMapper;

  public CustomUserDetailsService(AuthMapper authMapper) {
    this.authMapper = authMapper;
  }

  @Override
  public User loadUserByUsername(String name) throws UsernameNotFoundException {
    User user = authMapper.findByUsername(name);
    if (user == null) {
      throw new UsernameNotFoundException(String.format("No user found with username '%s'.", name));
    }
    Role role = authMapper.findRoleByUserId(user.getId());
    user.setRole(role);
    return user;
  }
}

登陸邏輯:

 public ResponseUserToken login(String username, String password) {
    //用戶驗(yàn)證
    final Authentication authentication = authenticate(username, password);
    //存儲(chǔ)認(rèn)證信息
    SecurityContextHolder.getContext().setAuthentication(authentication);
    //生成token
    final User user = (User) authentication.getPrincipal();
//    User user = (User) userDetailsService.loadUserByUsername(username);
    final String token = jwtTokenUtil.generateAccessToken(user);
    //存儲(chǔ)token
    jwtTokenUtil.putToken(username, token);
    return new ResponseUserToken(token, user);
  }

private Authentication authenticate(String username, String password) {
    try {
      //該方法會(huì)去調(diào)用userDetailsService.loadUserByUsername()去驗(yàn)證用戶名和密碼,如果正確,則存儲(chǔ)該用戶名密碼到“security 的 context中”
      return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
    } catch (DisabledException | BadCredentialsException e) {
      throw new CustomException(ResultJson.failure(ResultCode.LOGIN_ERROR, e.getMessage()));
    }
  }

自定義異常:

@Getter
public class CustomException extends RuntimeException{
  private ResultJson resultJson;

  public CustomException(ResultJson resultJson) {
    this.resultJson = resultJson;
  }
}

統(tǒng)一異常處理:

/**
 * 異常處理類
 * controller層異常無(wú)法捕獲處理,需要自己處理
 * Created by jt on 2018/8/27.
 */
@RestControllerAdvice
@Slf4j
public class DefaultExceptionHandler {

  /**
   * 處理所有自定義異常
   * @param e
   * @return
   */
  @ExceptionHandler(CustomException.class)
  public ResultJson handleCustomException(CustomException e){
    log.error(e.getResultJson().getMsg().toString());
    return e.getResultJson();
  }
}

所有經(jīng)controller轉(zhuǎn)發(fā)的請(qǐng)求拋出的自定義異常都會(huì)被捕獲處理,一般情況下就是返回給調(diào)用方一個(gè)json的報(bào)錯(cuò)信息,包含自定義狀態(tài)碼、錯(cuò)誤信息及補(bǔ)充描述信息。

值得注意的是,在請(qǐng)求到達(dá)controller之前,會(huì)被Filter攔截,如果在controller或者之前拋出的異常,自定義的異常處理器是無(wú)法處理的,需要自己重新定義一個(gè)全局異常處理器或者直接處理。

Filter攔截請(qǐng)求兩次的問(wèn)題

跨域的post的請(qǐng)求會(huì)驗(yàn)證兩次,get不會(huì)。網(wǎng)上的解釋是,post請(qǐng)求第一次是預(yù)檢請(qǐng)求,Request Method: OPTIONS。
解決方法:

在webSecurityConfig里添加

.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()

就可以不攔截options請(qǐng)求了。

這里只給出了最主要的代碼,還有controller層的訪問(wèn)權(quán)限設(shè)置,返回狀態(tài)碼,返回類定義等等。

看完這篇關(guān)于如何實(shí)現(xiàn)SpringBoot集成SpringSecurity和JWT做登陸鑒權(quán)的文章,如果覺(jué)得文章內(nèi)容寫得不錯(cuò)的話,可以把它分享出去給更多人看到。


當(dāng)前名稱:如何實(shí)現(xiàn)SpringBoot集成SpringSecurity和JWT做登陸鑒權(quán)
鏈接分享:http://weahome.cn/article/gijsio.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部