<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Spring-Security & JWT 實現(xiàn) token

          共 16387字,需瀏覽 33分鐘

           ·

          2022-07-27 02:59


          一、JWT

          //www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html

          二、項目環(huán)境搭建

          2.1 引入依賴

          pom.xml

          <dependencies>        <dependency>            <groupId>javax.xml.bind</groupId>            <artifactId>jaxb-api</artifactId>            <version>2.3.0</version>        </dependency>        <dependency>            <groupId>com.sun.xml.bind</groupId>            <artifactId>jaxb-impl</artifactId>            <version>2.3.0</version>        </dependency>        <dependency>            <groupId>com.sun.xml.bind</groupId>            <artifactId>jaxb-core</artifactId>            <version>2.3.0</version>        </dependency>        <dependency>            <groupId>javax.activation</groupId>            <artifactId>activation</artifactId>            <version>1.1.1</version>        </dependency>
          <!-- spring security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- jwt --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
          <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
          <!-- mybatisplus與springboot整合 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.2.0</version> </dependency> <!-- mybatis plus 代碼生成器依賴 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.2.0</version> </dependency>
          <!--swagger--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency>
          <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- mysql驅(qū)動 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency>
          </dependencies>
          <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
          </project>

          主要是要引入Spring Secruity和jwt的依賴。

          2.2 實體類(User)

          User.java

          public class User implements Serializable {    @TableId(value = "user_id",type= IdType.AUTO)    private int userId;    private String userName;    private String Password;    private String userAge;    private String Role;
          public int getUserId() { return userId; }
          public void setUserId(int userId) { this.userId = userId; }
          public String getUserName() { return userName; }
          public void setUserName(String userName) { this.userName = userName; }
          public String getPassword() { return Password; }
          public void setPassword(String password) { Password = password; }
          public String getUserAge() { return userAge; }
          public void setUserAge(String userAge) { this.userAge = userAge; }
          public String getRole() { return Role; }
          public void setRole(String role) { Role = role; }}

          2.3 jwt工具類

          JwtTokenUtils.java

          public class JwtTokenUtils {
          public static final String TOKEN_HEADER = "token"; public static final String TOKEN_PREFIX = "";
          private static final String SECRET = "jwtsecretdemo"; private static final String ISS = "echisan";
              // 過期時間是3600秒,即是1個小時 private static final long EXPIRATION = 3600L;
          // 選擇了記住我之后的過期時間為7天 private static final long EXPIRATION_REMEMBER = 604800L;
          // 創(chuàng)建token public static String createToken(String username, boolean isRememberMe) { long expiration = isRememberMe ? EXPIRATION_REMEMBER : EXPIRATION; return Jwts.builder() .signWith(SignatureAlgorithm.HS512, SECRET) .setIssuer(ISS) .setSubject(username) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + expiration * 1000)) .compact(); }
          // 從token中獲取用戶名 public static String getUsername(String token){ return getTokenBody(token).getSubject(); }
          // 是否已過期 public static boolean isExpiration(String token){ return getTokenBody(token).getExpiration().before(new Date()); }
          private static Claims getTokenBody(String token){ return Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(token) .getBody(); }}

          jwt工具類,對jjwt封裝一下方便調(diào)用。

          2.4 Dao層

          因為使用的是mybatis plus,所以沒有使用mapper.xml書寫sql語句,直接調(diào)用提供的CRUD。

          UserDao.java

          public interface UserDao extends BaseMapper<User> {
          }

          2.5 ServiceImpl層

          UserDetailsServiceImpl.java

          @Servicepublic class UserDetailsServiceImpl implements UserDetailsService {    @Autowired    private UserDao userDao;
          @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { Map<String, Object> map = new HashMap<>(); map.put("user_name", s); User user = userDao.selectByMap(map).get(0); return new JwtUser(user); }}
          • 注意:這個serviceImpl實現(xiàn)的接口UserDetailsService是框架提供的。

          • 使用springSecurity需要實現(xiàn)UserDetailsService接口供權(quán)限框架調(diào)用,該方法只需要實現(xiàn)一個方法就可以了,那就是根據(jù)用戶名去獲取用戶,這里使用的是mybatis plus提供的操作接口。

          接著去實現(xiàn)一下剛才返回的UserDetails

          public class JwtUser implements UserDetails {
          private Integer id; private String username; private String password; private Collection<? extends GrantedAuthority> authorities;
          public JwtUser() { }
          // 寫一個能直接使用user創(chuàng)建jwtUser的構(gòu)造器 public JwtUser(User user) { id = user.getUserId(); username = user.getUserName(); password = user.getPassword(); authorities = Collections.singleton(new SimpleGrantedAuthority(user.getRole())); }
          // 獲取權(quán)限信息,目前博主只會拿來存角色。。 @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; }
          @Override public String getPassword() { return password; }
          @Override public String getUsername() { return username; }
          // 賬號是否未過期,默認(rèn)是false,記得要改一下 @Override public boolean isAccountNonExpired() { return true; }
          // 賬號是否未鎖定,默認(rèn)是false,記得也要改一下 @Override public boolean isAccountNonLocked() { return true; }
          // 賬號憑證是否未過期,默認(rèn)是false,記得還要改一下 @Override public boolean isCredentialsNonExpired() { return true; }
          // 這個有點(diǎn)抽象不會翻譯,默認(rèn)也是false,記得改一下 @Override public boolean isEnabled() { return true; }
          // 我自己重寫打印下信息看的 @Override public String toString() { return "JwtUser{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + ", authorities=" + authorities + '}'; }}

          三、配置攔截器

          這邊需要實現(xiàn)兩個過濾器。使用JWTAuthenticationFilter去進(jìn)行用戶賬號的驗證,使用JWTAuthorizationFilter去進(jìn)行用戶權(quán)限的驗證。

          3.1 JWTAuthenticationFilter

          JWTAuthenticationFilter繼承于UsernamePasswordAuthenticationFilter

          該攔截器用于獲取用戶登錄的信息,只需創(chuàng)建一個token并調(diào)用authenticationManager.authenticate()讓spring-security去進(jìn)行驗證就可以了,不用自己查數(shù)據(jù)庫再對比密碼了,這一步交給spring去操作。

          這個操作有點(diǎn)像是shiro的subject.login(new UsernamePasswordToken()),驗證的事情交給框架。

          JWTAuthenticationFilter.java

          public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
          private AuthenticationManager authenticationManager;
          public JWTAuthenticationFilter(AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; }
          @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
          // 從輸入流中獲取到登錄的信息 try { LoginUser loginUser = new ObjectMapper().readValue(request.getInputStream(), LoginUser.class); return authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(loginUser.getUsername(), loginUser.getPassword(), new ArrayList<>()) ); } catch (IOException e) { e.printStackTrace(); return null; } }
          // 成功驗證后調(diào)用的方法 // 如果驗證成功,就生成token并返回 @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
          // 查看源代碼會發(fā)現(xiàn)調(diào)用getPrincipal()方法會返回一個實現(xiàn)了`UserDetails`接口的對象 // 所以就是JwtUser啦 JwtUser jwtUser = (JwtUser) authResult.getPrincipal(); System.out.println("jwtUser:" + jwtUser.toString()); String token = JwtTokenUtils.createToken(jwtUser.getUsername(), false); // 返回創(chuàng)建成功的token // 但是這里創(chuàng)建的token只是單純的token // 按照jwt的規(guī)定,最后請求的格式應(yīng)該是 `Bearer token` response.setHeader("token", JwtTokenUtils.TOKEN_PREFIX + token); }
          // 這是驗證失敗時候調(diào)用的方法 @Override protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { response.getWriter().write("authentication failed, reason: " + failed.getMessage()); }}

          這里還用到了LoginUser這個實體類,也是需要自己定義一下的。

          LoginUser.java

          public class LoginUser {
          private String username; private String password; private Integer rememberMe;
          public String getUsername() { return username; }
          public void setUsername(String username) { this.username = username; }
          public String getPassword() { return password; }
          public void setPassword(String password) { this.password = password; }
          public Integer getRememberMe() { return rememberMe; }
          public void setRememberMe(Integer rememberMe) { this.rememberMe = rememberMe; }}

          3.2 JWTAuthorizationFilter

          驗證成功當(dāng)然就是進(jìn)行鑒權(quán)了,每一次需要權(quán)限的請求都需要檢查該用戶是否有該權(quán)限去操作該資源,當(dāng)然這也是框架幫我們做的,那么我們需要做什么呢?很簡單,只要告訴spring-security該用戶是否已登錄,是什么角色,擁有什么權(quán)限就可以了。

          JWTAuthenticationFilter繼承于BasicAuthenticationFilter,至于為什么要繼承這個我也不太清楚了,這個我也是網(wǎng)上看到的其中一種實現(xiàn),實在springSecurity苦手,不過我覺得不繼承這個也沒事呢(實現(xiàn)以下filter接口或者繼承其他filter實現(xiàn)子類也可以吧)只要確保過濾器的順序,JWTAuthorizationFilter在JWTAuthenticationFilter后面就沒問題了。

          JWTAuthorizationFilter.java

          public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
          public JWTAuthorizationFilter(AuthenticationManager authenticationManager) { super(authenticationManager); }
          @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
          String tokenHeader = request.getHeader(JwtTokenUtils.TOKEN_HEADER); // 如果請求頭中沒有Authorization信息則直接放行了 if (tokenHeader == null || !tokenHeader.startsWith(JwtTokenUtils.TOKEN_PREFIX)) { chain.doFilter(request, response); return; } // 如果請求頭中有token,則進(jìn)行解析,并且設(shè)置認(rèn)證信息 SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader)); super.doFilterInternal(request, response, chain); }
          // 這里從token中獲取用戶信息并新建一個token private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) { String token = tokenHeader.replace(JwtTokenUtils.TOKEN_PREFIX, ""); String username = JwtTokenUtils.getUsername(token); if (username != null){ return new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>()); } return null; }}

          3.3 配置SpringSecurity

          需要開啟一下注解@EnableWebSecurity然后再繼承一下WebSecurityConfigurerAdapter就可以啦

          WebSecurityConfig.java

          @Configurationpublic class WebSecurityConfig extends WebSecurityConfigurerAdapter {
          @Autowired // 因為UserDetailsService的實現(xiàn)類實在太多啦,這里設(shè)置一下我們要注入的實現(xiàn)類 @Qualifier("userDetailsServiceImpl") private UserDetailsService userDetailsService;
          // 加密密碼的,安全第一嘛~ @Bean public BCryptPasswordEncoder bCryptPasswordEncoder(){ return new BCryptPasswordEncoder(); }
          @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder()); }
          @Override protected void configure(HttpSecurity http) throws Exception { http.cors().and().csrf().disable() .authorizeRequests() // 測試用資源,需要驗證了的用戶才能訪問 .antMatchers("/tasks/**").authenticated() // 其他都放行了 .anyRequest().permitAll() .and() .addFilter(new JWTAuthenticationFilter(authenticationManager())) .addFilter(new JWTAuthorizationFilter(authenticationManager())) // 不需要session .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); }
          @Bean CorsConfigurationSource corsConfigurationSource() { final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues()); return source; }}

          四、測試

          4.1 注冊

          AuthController.java

          @RestController@RequestMapping("/auth")public class AuthController {
          // 為了減少篇幅就不寫service接口了 @Autowired private UserDao userDao;
          @Autowired private BCryptPasswordEncoder bCryptPasswordEncoder;
          @PostMapping("/register") public String registerUser(@RequestBody Map<String,String> registerUser){ User user = new User(); user.setUserName(registerUser.get("username")); // 記得注冊的時候把密碼加密一下 user.setPassword(bCryptPasswordEncoder.encode(registerUser.get("password"))); user.setRole("ROLE_USER"); int result = userDao.insert(user); return Integer.toString(result); }}

          4.2 登陸

          根據(jù)UsernamePasswordAuthenticationFilter的源代碼,可以看出登錄默認(rèn)是/login

          public UsernamePasswordAuthenticationFilter() {    super(new AntPathRequestMatcher("/login", "POST"));}

          當(dāng)然也可以自定義,只需要在JWTAuthenticationFilter的構(gòu)造方法中加入下面那一句話就可以啦

          public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {        this.authenticationManager = authenticationManager;        super.setFilterProcessesUrl("/auth/login");}


          4.3 接口驗證

          helloController.java

          @RestController@RequestMapping("/tasks")public class HelloController {    @GetMapping("/hello")    public String hello() {        return "hello jwt !";    }    @GetMapping("/admin")    public String admin() {        return "hello admin !";    }}

          4.4 測試結(jié)果

          • 先是注冊

          • 登陸

          這是可以獲取 token


          • 接口訪問測試

          需要將 token 加上才可以訪問成功。



          記得點(diǎn)「」和「在看」↓

          愛你們


          瀏覽 50
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  午夜精品人妻无码 | 欧美一级免费看 | 在线吴梦梦视频一区二区 | 黄片在线免费 | 国产精品色综合 |