spring-boot+mybatis+restful api+jwt登陸(2)

前文回顧spring-boot+mybatis+restful api+jwt登陸(1)

用spring-boot開發(fā)RESTful API非常的方便,在生產(chǎn)環(huán)境中,對(duì)發(fā)布的API增加授權(quán)保護(hù)是非常必要的?,F(xiàn)在我們來看如何利用JWT技術(shù)為API增加授權(quán)保護(hù),保證只有獲得授權(quán)的用戶才能夠訪問API。

1. 引入security和jwt依賴

前文已經(jīng)引入了這兩個(gè)包,這里再看一下這兩個(gè)是如何引入的

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.7.0</version>
        </dependency>

2. 增加注冊功能

利用前文引入的mybatis自動(dòng)生成代碼的插件,生成model和mapper

  • User類,省略setter和getter方法
public class User {
    private String id;

    private String username;

    private String password;

    private String email;

    private String mobile;

    private String loginIp;

    private Date loginTime;

    private Byte isAviliable;

    private Integer type;

    private String avatar;
}
  • UserMapper
public interface UserMapper {
    int deleteByPrimaryKey(String id);

    int insert(User record);

    int insertSelective(User record);

    User selectByPrimaryKey(String id);

    int updateByPrimaryKeySelective(User record);

    int updateByPrimaryKey(User record);

    User findByUsername(String username);
}

插件還會(huì)生成對(duì)應(yīng)的mapper.xml,具體代碼不再貼出了


創(chuàng)建UserService類,加入signup方法

@Service
public class UserService {
    @Autowired
    UserMapper userMapper;
    @Autowired
    BCryptPasswordEncoder bCryptPasswordEncoder;

    public User signup(User user) {
        user.setId(UUID.randomUUID().toString());
        user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
        userMapper.insertSelective(user);
        return user;
    }
}

加入控制層代碼

@RestController
@RequestMapping("api/user")
public class UserController {

    @Autowired
    UserService userService;

    @PostMapping(value = "/signup")
    public User signup(@RequestBody User user) {
        user = userService.signup(user);
        return user;
    }
}

密碼采用了BCryptPasswordEncoder進(jìn)行加密,我們在啟動(dòng)類中增加BCryptPasswordEncoder實(shí)例的定義。

@SpringBootApplication
@MapperScan("com.itcuc.qaserver.mapper")
@ServletComponentScan
public class QaserverApplication {

    public static void main(String[] args) {
        SpringApplication.run(QaserverApplication.class, args);
    }

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

3. 增加jwt認(rèn)證功能

用戶填入用戶名密碼后,與數(shù)據(jù)庫里存儲(chǔ)的用戶信息進(jìn)行比對(duì),如果通>過,則認(rèn)證成功。傳統(tǒng)的方法是在認(rèn)證通過后,創(chuàng)建sesstion,并給客戶端返回cookie?,F(xiàn)在我們采用JWT來處理用戶名密碼的認(rèn)證。區(qū)別在于,認(rèn)證通過后,服務(wù)器生成一個(gè)token,將token返回給客戶端,客戶端以后的所有請求都需要在http頭中指定該token。服務(wù)器接收的請求后,會(huì)對(duì)token的合法性進(jìn)行驗(yàn)證。驗(yàn)證的內(nèi)容包括:

  1. 內(nèi)容是一個(gè)正確的JWT格式
  2. 檢查簽名
  3. 檢查claims
  4. 檢查權(quán)限
處理登錄

創(chuàng)建一個(gè)類JWTLoginFilter,核心功能是在驗(yàn)證用戶名密碼正確后,生成一個(gè)token,并將token返回給客戶端:

public class JWTLoginFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;

    public JWTLoginFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    // 接收并解析用戶憑證
    @Override
    public Authentication attemptAuthentication(HttpServletRequest req,
                                                HttpServletResponse res) throws AuthenticationException {
        try {
            User user = new ObjectMapper()
                    .readValue(req.getInputStream(), User.class);

            return authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(
                            user.getUsername(),
                            user.getPassword(),
                            new ArrayList<>())
            );
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    // 用戶成功登錄后,這個(gè)方法會(huì)被調(diào)用,我們在這個(gè)方法里生成token
    @Override
    protected void successfulAuthentication(HttpServletRequest req,
                                            HttpServletResponse res,
                                            FilterChain chain,
                                            Authentication auth) throws IOException, ServletException {

        String token = Jwts.builder()
                .setSubject(((org.springframework.security.core.userdetails.User) auth.getPrincipal()).getUsername())
                .setExpiration(new Date(System.currentTimeMillis() + 60 * 60 * 24 * 1000))
                .signWith(SignatureAlgorithm.HS512, "MyJwtSecret")
                .compact();
        res.addHeader("Authorization", "Bearer " + token);
    }

}

該類繼承自UsernamePasswordAuthenticationFilter,重寫了其中的2個(gè)方法:

attemptAuthentication :接收并解析用戶憑證。

successfulAuthentication :用戶成功登錄后,這個(gè)方法會(huì)被調(diào)用,我們在這個(gè)方法里生成token。

授權(quán)驗(yàn)證

用戶一旦登錄成功后,會(huì)拿到token,后續(xù)的請求都會(huì)帶著這個(gè)token,服務(wù)端會(huì)驗(yàn)證token的合法性。

創(chuàng)建JWTAuthenticationFilter類,我們在這個(gè)類中實(shí)現(xiàn)token的校驗(yàn)功能。

public class JWTAuthenticationFilter extends BasicAuthenticationFilter {

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

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String header = request.getHeader("Authorization");

        if (header == null || !header.startsWith("Bearer ")) {
            chain.doFilter(request, response);
            return;
        }

        UsernamePasswordAuthenticationToken authentication = getAuthentication(request);

        SecurityContextHolder.getContext().setAuthentication(authentication);
        chain.doFilter(request, response);

    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
        String token = request.getHeader("Authorization");
        if (token != null) {
            // parse the token.
            String user = Jwts.parser()
                    .setSigningKey("MyJwtSecret")
                    .parseClaimsJws(token.replace("Bearer ", ""))
                    .getBody()
                    .getSubject();

            if (user != null) {
                return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
            }
            return null;
        }
        return null;
    }

}

該類繼承自BasicAuthenticationFilter,在doFilterInternal方法中,從http頭的Authorization 項(xiàng)讀取token數(shù)據(jù),然后用Jwts包提供的方法校驗(yàn)token的合法性。如果校驗(yàn)通過,就認(rèn)為這是一個(gè)取得授權(quán)的合法請求。

SpringSecurity配置

通過SpringSecurity的配置,將上面的方法組合在一起。

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

    private UserDetailsService userDetailsService;

    private BCryptPasswordEncoder bCryptPasswordEncoder;

    public WebSecurityConfig(UserDetailsService userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) {
        this.userDetailsService = userDetailsService;
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable().authorizeRequests()
                .antMatchers(HttpMethod.POST, "/api/user/signup").permitAll()
                .anyRequest().authenticated()
                .and()
                .addFilter(new JWTLoginFilter(authenticationManager()))
                .addFilter(new JWTAuthenticationFilter(authenticationManager()));
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
    }

}

這是標(biāo)準(zhǔn)的SpringSecurity配置內(nèi)容,就不在詳細(xì)說明。注意其中的

.addFilter(new JWTLoginFilter(authenticationManager()))
.addFilter(new JwtAuthenticationFilter(authenticationManager()))

這兩行,將我們定義的JWT方法加入SpringSecurity的處理流程中。

(以上內(nèi)容引用自https://blog.csdn.net/sxdtzhaoxinguo/article/details/77965226)

這里需要實(shí)現(xiàn)UserDetailsService接口

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    private UserMapper userMapper;

    /**
     * 通過構(gòu)造器注入U(xiǎn)serRepository
     * @param userMapper
     */
    public UserDetailsServiceImpl(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userMapper.findByUsername(username);
        if(user == null){
            throw new UsernameNotFoundException(username);
        }
        return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), emptyList());
    }

}

這時(shí)再請求hello接口,會(huì)返回403錯(cuò)誤


image.png

下面注冊一個(gè)新用戶


image.png

使用新注冊的用戶登錄,會(huì)返回token,在http header中,Authorization: Bearer 后面的部分就是token


image.png

然后我們使用這個(gè)token再訪問hello接口
image.png

至此,功能完成

參考感謝:

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

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

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