赞
踩
关于什么是jwt(json web token),还有jwt的工作流程我这边就不多介绍,主要给大家介绍一下SpringBoot中如何整合Security然后在添加jwt的支持。
史上最全Java技术栈面试题都在这里面了:Java面试题
通过SpringBoot+Security+JWT来实现token校验的过程。在生产环境中,对发布API增加授权保护是非常必要的一件事情,这里我们就是利用jwt生成token,利用token来进行接口安全验证。
在整合jwt之前,我们首先要在SpringBoot中整合security的模块,来实现基于security的授权控制。用过security的人都知道,它的功能无比的强大比shiro还要强大,但是今天我就介绍security中关于权限控制和是登录验证的这一块的功能。
1.在开始之前我们先在pom.xml中添加security和jwt的相关依赖,如下所示:
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <groupId>org.sang</groupId>
- <artifactId>test26-security</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- <packaging>jar</packaging>
- <name>Test26-Security</name>
- <description>Demo project for Spring Boot</description>
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>1.4.3.RELEASE</version>
- <relativePath/> <!-- lookup parent from repository -->
- </parent>
- <properties>
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
- <java.version>1.8</java.version>
- </properties>
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-jpa</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-security</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-thymeleaf</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.thymeleaf.extras</groupId>
- <artifactId>thymeleaf-extras-springsecurity4</artifactId>
- </dependency>
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <version>5.1.40</version>
- </dependency>
- <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
- <dependency>
- <groupId>io.jsonwebtoken</groupId>
- <artifactId>jjwt</artifactId>
- <version>0.9.0</version>
- </dependency>
- </dependencies>
- <build>
- <plugins>
- <plugin>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-maven-plugin</artifactId>
- </plugin>
- </plugins>
- </build>
- </project>
2.在application.properties中添加数据库和jpa还有jwt的相关配置。具体如下所示:
spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/security_db?useUnicode=true&characterEncoding=utf-8 spring.datasource.username=root spring.datasource.password=220316 logging.level.org.springframework.security=info spring.thymeleaf.cache=false spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true spring.jackson.serialization.indent_output=true # JWT 604800 jwt.header:Authorization jwt.secret:mySecret jwt.expiration:10 jwt.tokenHead:Bearer jwt.route.authentication.path:auth/login jwt.route.authentication.refresh:auth/refresh jwt.route.authentication.register:"auth/register"
3.新建实体类,进行用户登录的授权验证。(这里我新建两个实体类,一个是用户实体类,一个是用户关系实体类)
用户实体类:SysUser
- package org.security.entity;
- import org.springframework.security.core.GrantedAuthority;
- import org.springframework.security.core.authority.SimpleGrantedAuthority;
- import org.springframework.security.core.userdetails.UserDetails;
- import javax.persistence.*;
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.Date;
- import java.util.List;
- @Entity
- public class SysUser implements UserDetails {
- @Id
- @GeneratedValue
- private Long id;
- private String username;
- private String password;
- private Date lastPasswordResetDate;
- @ManyToMany(cascade = {CascadeType.REFRESH},fetch = FetchType.EAGER)
- private List<SysRole> roles;
- public Long getId() {
- return id;
- }
-
- public void setId(Long id) {
- this.id = id;
- }
-
- public void setUsername(String username) {
- this.username = username;
- }
-
- public void setPassword(String password) {
- this.password = password;
- }
-
- public List<SysRole> getRoles() {
- return roles;
- }
-
- public void setRoles(List<SysRole> roles) {
- this.roles = roles;
- }
-
- public Date getLastPasswordResetDate() {
- return lastPasswordResetDate;
- }
-
- public void setLastPasswordResetDate(Date lastPasswordResetDate) {
- this.lastPasswordResetDate = lastPasswordResetDate;
- }
-
- @Override
- public Collection<? extends GrantedAuthority> getAuthorities() {
- List<GrantedAuthority> auths = new ArrayList<>();
- List<SysRole> roles = this.getRoles();
- for (SysRole role : roles) {
- auths.add(new SimpleGrantedAuthority(role.getName()));
- }
- return auths;
- }
-
- @Override
- public String getPassword() {
- return this.password;
- }
-
- @Override
- public String getUsername() {
- return this.username;
- }
-
- @Override
- public boolean isAccountNonExpired() {
- return true;
- }
-
- @Override
- public boolean isAccountNonLocked() {
- return true;
- }
-
- @Override
- public boolean isCredentialsNonExpired() {
- return true;
- }
-
- @Override
- public boolean isEnabled() {
- return true;
- }
- }
因为SysUser实体类继承UserDetails接口,这个类是基于Security的安全服务。所以通过继承UserDetails这个类,我们就可以实现Security中相关的功能了,后面我们会具体介绍是怎么一回事。
用户关系实体类:SysRole
- package org.security.entity;
- import javax.persistence.Entity;
- import javax.persistence.GeneratedValue;
- import javax.persistence.Id;
- @Entity
- public class SysRole {
- @Id
- @GeneratedValue
- private Long id;
- private String name;
-
-
- public Long getId() {
- return id;
- }
-
- public void setId(Long id) {
- this.id = id;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
- }
SysRole实体类是保存着用户的拥有什么样权限的实体类。和用户实体是多对一的关系,也就是一个用户可以拥有多个权限。
实体类创建好之后,我们就可以编写相应的dao,service,controller类来实现用户登录的功能了。这里我会新建一个login.html页面和index.html页面,来进行登录校验的和权限控制演示。
dao接口(这里我采用jpa来实现用户查询的功能)
- package org.security.dao;
- import org.security.entity.SysUser;
- import org.springframework.data.jpa.repository.JpaRepository;
- public interface SysUserRepository extends JpaRepository<SysUser, Long> {
- SysUser findByUsername(String username);
- }
service业务类
- package org.security.service;
- import org.security.dao.SysUserRepository;
- import org.security.entity.SysUser;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.security.core.userdetails.UserDetails;
- import org.springframework.security.core.userdetails.UserDetailsService;
- import org.springframework.security.core.userdetails.UsernameNotFoundException;
- public class CustomUserService implements UserDetailsService {
- @Autowired
- SysUserRepository userRepository;
- @Override
- public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
- SysUser user = userRepository.findByUsername(s);
- if (user == null) {
- throw new UsernameNotFoundException("用户名不存在");
- }
- System.out.println("s:"+s);
- System.out.println("username:"+user.getUsername()+";password:"+user.getPassword());
- System.out.println("size:"+user.getRoles().size());
- System.out.println("role:"+user.getRoles().get(0).getName());
- return user;
- }
- }
controller控制类:
- package org.security.controller;
- import org.security.entity.Msg;
- import org.springframework.security.access.prepost.PreAuthorize;
- import org.springframework.stereotype.Controller;
- import org.springframework.ui.Model;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestMethod;
- import org.springframework.web.bind.annotation.ResponseBody;
-
- /**
- * Created by linzhiqiang on 2017/10/26
- */
- @Controller
- public class HomeController {
- @RequestMapping("/")
- public String index(Model model) {
- Msg msg = new Msg("测试标题", "测试内容", "额外信息,只对管理员显示");
- model.addAttribute("msg", msg);
- return "index";
- }
-
- @PreAuthorize("hasAuthority('ROLE_USER')")
- @RequestMapping(value="/admin/test1")
- @ResponseBody
- public String adminTest1() {
- return "ROLE_USER";
- }
-
- @PreAuthorize("hasAuthority('ROLE_ADMIN')")
- @RequestMapping("/admin/test2")
- @ResponseBody
- public String adminTest2() {
- return "ROLE_ADMIN";
- }
- }
login.html页面:
- <!DOCTYPE html>
- <html lang="en" xmlns:th="http://www.thymeleaf.org">
- <head>
- <meta charset="UTF-8"/>
- <title>登录</title>
- <link rel="stylesheet" th:href="@{css/bootstrap.min.css}"/>
- <link rel="stylesheet" th:href="@{css/signin.css}"/>
- <style type="text/css">
- body {
- padding-top: 50px;
- }
- .starter-template {
- padding: 40px 15px;
- text-align: center;
- }
- </style>
- </head>
- <body>
- <nav class="navbar navbar-inverse navbar-fixed-top">
- <div class="container">
- <div class="navbar-header">
- <a class="navbar-brand" href="#">Spring Security演示</a>
- </div>
- <div id="navbar" class="collapse navbar-collapse">
- <ul class="nav navbar-nav">
- <li><a th:href="@{/}">首页</a></li>
- <li><a th:href="@{http://www.baidu.com}">百度</a></li>
- </ul>
- </div>
- </div>
- </nav>
- <div class="container">
- <div class="starter-template">
- <p th:if="${param.logout}" class="bg-warning">已注销</p>
- <p th:if="${param.error}" class="bg-danger">有错误,请重试</p>
- <h2>使用账号密码登录</h2>
- <form class="form-signin" role="form" name="form" th:action="@{/login}" action="/login" method="post">
- <div class="form-group">
- <label for="username">账号</label>
- <input type="text" class="form-control" name="username" value="" placeholder="账号"/>
- </div>
- <div class="form-group">
- <label for="password">密码</label>
- <input type="password" class="form-control" name="password" placeholder="密码"/>
- </div>
- <input type="submit" id="login" value="Login" class="btn btn-primary"/>
- </form>
- </div>
- </div>
- </body>
- </html>
index.html页面:
- <!DOCTYPE html>
- <html lang="en" xmlns:th="http://www.thymeleaf.org"
- xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
- <head>
- <meta charset="UTF-8"/>
- <title sec:authentication="name"></title>
- <link rel="stylesheet" th:href="@{css/bootstrap.min.css}"/>
- <style type="text/css">
- body {
- padding-top: 50px;
- }
-
- .starter-template {
- padding: 40px 15px;
- text-align: center;
- }
- </style>
- </head>
- <body>
- <nav class="navbar navbar-inverse navbar-fixed-top">
- <div class="container">
- <div class="navbar-header">
- <a class="navbar-brand" href="#">Spring Security演示</a>
- </div>
- <div id="navbar" class="collapse navbar-collapse">
- <ul class="nav navbar-nav">
- <li><a th:href="@{/}">首页</a></li>
- <li><a th:href="@{http://www.baidu.com}">百度</a></li>
- </ul>
- </div>
- </div>
- </nav>
- <div class="container">
- <div class="starter-template">
- <h1 th:text="${msg.title}"></h1>
- <p class="bg-primary" th:text="${msg.content}"></p>
- <div sec:authorize="hasRole('ROLE_ADMIN')">
- <p class="bg-info" th:text="${msg.extraInfo}"></p>
- </div>
- <div sec:authorize="hasRole('ROLE_USER')">
- <p class="bg-info">无更多显示信息</p>
- </div>
- <form th:action="@{/logout}" method="post">
- <input type="submit" class="btn btn-primary" value="注销"/>
- </form>
- </div>
- </div>
- </body>
- </html>
4.SpringBoot中配置基于security的支持,这里我有两个配置
一个是没有登录用户都跳转到login.html页面的配置:
- package org.security.config;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
- import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
- @Configuration
- public class WebMvcConfig extends WebMvcConfigurerAdapter {
- @Override
- public void addViewControllers(ViewControllerRegistry registry) {
- registry.addViewController("/login").setViewName("login");
- }
- }
接下来就是security核心的配置类了:
- package org.security.config;
- import org.security.service.CustomUserService;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.http.HttpMethod;
- import org.springframework.security.authentication.AuthenticationManager;
- import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
- import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
- import org.springframework.security.config.annotation.web.builders.HttpSecurity;
- import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
- import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
- import org.springframework.security.config.http.SessionCreationPolicy;
- import org.springframework.security.core.userdetails.UserDetailsService;
- import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
- import org.springframework.security.crypto.password.PasswordEncoder;
- import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
- @Configuration
- @EnableWebSecurity
- @EnableGlobalMethodSecurity(prePostEnabled = true)
- public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
- @Bean
- protected UserDetailsService customUserService() {
- return new CustomUserService();
- }
-
- @Autowired
- public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
- authenticationManagerBuilder
- // 设置UserDetailsService
- .userDetailsService(customUserService())
- // 使用BCrypt进行密码的hash
- .passwordEncoder(passwordEncoder());
- }
-
- // 装载BCrypt密码编码器
- @Bean
- public PasswordEncoder passwordEncoder() {
- return new BCryptPasswordEncoder();
- }
-
- @Bean
- public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
- return new JwtAuthenticationTokenFilter();
- }
-
- @Override
- protected void configure(AuthenticationManagerBuilder auth) throws Exception {
- auth.userDetailsService(customUserService());
- }
-
- @Bean
- @Override
- public AuthenticationManager authenticationManagerBean() throws Exception {
- return super.authenticationManagerBean();
- }
-
- @Override
- protected void configure(HttpSecurity httpSecurity) throws Exception {
- httpSecurity
- // 由于使用的是JWT,我们这里不需要csrf
- // .csrf().disable()
- // 基于token,所以不需要session
- // .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
- .authorizeRequests()
- //.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
- // 允许对于网站静态资源的无授权访问
- .antMatchers(
- HttpMethod.GET,
- "/",
- "/*.html",
- "/favicon.ico",
- "/**/*.html",
- "/**/*.css",
- "/**/*.js"
- ).permitAll()
- // 对于获取token的rest api要允许匿名访问
- .antMatchers("/auth/**").permitAll()
- .antMatchers("/admin/**").hasIpAddress("127.0.0.1")
- .antMatchers("/admin/**").access("hasAuthority('ROLE_ADMIN')")
- .anyRequest().authenticated().and().formLogin().loginPage("/login")
- .failureUrl("/login?error").permitAll().and().logout().permitAll();
- // 除上面外的所有请求全部需要鉴权认证
- // .anyRequest().authenticated();
- // 添加JWT filter
- // httpSecurity
- // .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
- // 禁用缓存
- // httpSecurity.headers().cacheControl();
- }
- }
这里我暂时先把jwt相关的代码先注释掉,将注解取消掉的话,就可以实现jwt相关的功能了。
5.打开浏览器进行功能测试,下面是测试的结果:
如果未登录的话,就都会跳转到这个login.html登录页面
登录成功后进入的index页面,这里测试的账户权限类型是admin,下面有两个地址一个是admin/test1只能user权限才可以访问,还有一个是admin/test2拥有admin权限的人才可以访问,下面我们开始测试。
因为权限不够所以,返回的是403错误。
权限够的话,返回的是这个接口获取的数据。
到这里关于SpringBoot整合Security就介绍完了,接下来就是如何添加jwt的支持了。
6.基本过程和security差不多:一:首先创建一个工具类,用来生成jwt的token和token的验证刷新之类的。
二:然后创建一个fiter类,并配置注入security配置文件中
三:接下来就是创建dao,service,controller来实现接口。
四:最后就是利用postman来测试是否实现jwt功能
工具类JwtTokenUtil:
- package org.security.util;
- import io.jsonwebtoken.Claims;
- import io.jsonwebtoken.Jwts;
- import io.jsonwebtoken.SignatureAlgorithm;
- import org.security.entity.SysUser;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.security.core.userdetails.UserDetails;
- import org.springframework.stereotype.Component;
- import java.io.Serializable;
- import java.util.Date;
- import java.util.HashMap;
- import java.util.Map;
-
- @Component
- public class JwtTokenUtil implements Serializable {
-
- private static final long serialVersionUID = -3301605591108950415L;
-
- private static final String CLAIM_KEY_USERNAME = "sub";
- private static final String CLAIM_KEY_CREATED = "created";
-
- @Value("${jwt.secret}")
- private String secret;
-
- @Value("${jwt.expiration}")
- private Long expiration;
-
- public String getUsernameFromToken(String token) {
- String username;
- try {
- final Claims claims = getClaimsFromToken(token);
- username = claims.getSubject();
- } catch (Exception e) {
- username = null;
- }
- return username;
- }
-
- public Date getCreatedDateFromToken(String token) {
- Date created;
- try {
- final Claims claims = getClaimsFromToken(token);
- created = new Date((Long) claims.get(CLAIM_KEY_CREATED));
- } catch (Exception e) {
- created = null;
- }
- return created;
- }
-
- public Date getExpirationDateFromToken(String token) {
- Date expiration;
- try {
- final Claims claims = getClaimsFromToken(token);
- expiration = claims.getExpiration();
- } catch (Exception e) {
- expiration = null;
- }
- return expiration;
- }
-
- private Claims getClaimsFromToken(String token) {
- Claims claims;
- try {
- claims = Jwts.parser()
- .setSigningKey(secret)
- .parseClaimsJws(token)
- .getBody();
- } catch (Exception e) {
- claims = null;
- }
- return claims;
- }
-
- private Date generateExpirationDate() {
- return new Date(System.currentTimeMillis() + expiration * 1000);
- }
-
- private Boolean isTokenExpired(String token) {
- final Date expiration = getExpirationDateFromToken(token);
- return expiration.before(new Date());
- }
-
- private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
- return (lastPasswordReset != null && created.before(lastPasswordReset));
- }
-
- public String generateToken(UserDetails userDetails) {
- Map<String, Object> claims = new HashMap<>();
- claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
- claims.put(CLAIM_KEY_CREATED, new Date());
- return generateToken(claims);
- }
-
- String generateToken(Map<String, Object> claims) {
- return Jwts.builder()
- .setClaims(claims)
- .setExpiration(generateExpirationDate())
- .signWith(SignatureAlgorithm.HS512, secret)
- .compact();
- }
-
- public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {
- final Date created = getCreatedDateFromToken(token);
- return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)
- && !isTokenExpired(token);
- }
-
- public String refreshToken(String token) {
- String refreshedToken;
- try {
- final Claims claims = getClaimsFromToken(token);
- claims.put(CLAIM_KEY_CREATED, new Date());
- refreshedToken = generateToken(claims);
- } catch (Exception e) {
- refreshedToken = null;
- }
- return refreshedToken;
- }
-
- public Boolean validateToken(String token, UserDetails userDetails) {
- SysUser user = (SysUser) userDetails;
- final String username = getUsernameFromToken(token);
- final Date created = getCreatedDateFromToken(token);
- // final Date expiration = getExpirationDateFromToken(token);
- return (
- username.equals(user.getUsername())
- && !isTokenExpired(token)
- && !isCreatedBeforeLastPasswordReset(created, user.getLastPasswordResetDate()));
- }
- }
dao接口:
- package org.security.dao;
- import org.security.entity.SysUser;
- public interface AuthService {
- SysUser register(SysUser userToAdd);
- String login(String username, String password);
- String refresh(String oldToken);
- }
dao接口实现类(service业务类):
- package org.security.service;
- import org.security.dao.AuthService;
- import org.security.dao.SysUserRepository;
- import org.security.entity.SysUser;
- import org.security.util.JwtTokenUtil;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.security.authentication.AuthenticationManager;
- import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
- import org.springframework.security.core.Authentication;
- import org.springframework.security.core.context.SecurityContextHolder;
- import org.springframework.security.core.userdetails.UserDetails;
- import org.springframework.security.core.userdetails.UserDetailsService;
- import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
- import org.springframework.stereotype.Service;
-
- import java.util.Date;
-
- import static java.util.Arrays.asList;
-
- @Service
- public class AuthServiceImpl implements AuthService {
-
- private AuthenticationManager authenticationManager;
- private UserDetailsService userDetailsService;
- private JwtTokenUtil jwtTokenUtil;
- private SysUserRepository userRepository;
-
- @Value("${jwt.tokenHead}")
- private String tokenHead;
-
- @Autowired
- public AuthServiceImpl(
- AuthenticationManager authenticationManager,
- UserDetailsService userDetailsService,
- JwtTokenUtil jwtTokenUtil,
- SysUserRepository userRepository) {
- this.authenticationManager = authenticationManager;
- this.userDetailsService = userDetailsService;
- this.jwtTokenUtil = jwtTokenUtil;
- this.userRepository = userRepository;
- }
-
- @Override
- public SysUser register(SysUser userToAdd) {
- final String username = userToAdd.getUsername();
- if(userRepository.findByUsername(username)!=null) {
- return null;
- }
- BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
- final String rawPassword = userToAdd.getPassword();
- userToAdd.setPassword(encoder.encode(rawPassword));
- userToAdd.setLastPasswordResetDate(new Date());
- // userToAdd.setRoles(asList("ROLE_USER"));
- return userRepository.save(userToAdd);
- }
-
- @Override
- public String login(String username, String password) {
- UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(username, password);
- // Perform the security
- final Authentication authentication = authenticationManager.authenticate(upToken);
- SecurityContextHolder.getContext().setAuthentication(authentication);
- // Reload password post-security so we can generate token
- final UserDetails userDetails = userDetailsService.loadUserByUsername(username);
- final String token = jwtTokenUtil.generateToken(userDetails);
- return token;
- }
-
- @Override
- public String refresh(String oldToken) {
- final String token = oldToken.substring(tokenHead.length());
- String username = jwtTokenUtil.getUsernameFromToken(token);
- SysUser user = (SysUser) userDetailsService.loadUserByUsername(username);
- if (jwtTokenUtil.canTokenBeRefreshed(token, user.getLastPasswordResetDate())){
- return jwtTokenUtil.refreshToken(token);
- }
- return null;
- }
- }
controller控制类:
- package org.security.controller;
- import org.security.dao.AuthService;
- import org.security.entity.SysUser;
- import org.security.service.JwtAuthenticationRequest;
- import org.security.service.JwtAuthenticationResponse;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.http.ResponseEntity;
- import org.springframework.security.core.AuthenticationException;
- import org.springframework.web.bind.annotation.RequestBody;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestMethod;
- import org.springframework.web.bind.annotation.RestController;
-
- import javax.servlet.http.HttpServletRequest;
-
- @RestController
- public class AuthController {
- @Value("${jwt.header}")
- private String tokenHeader;
-
- @Autowired
- private AuthService authService;
-
- @RequestMapping(value = "/auth/login", method = RequestMethod.POST)
- public ResponseEntity<?> createAuthenticationToken(
- String username,String password
- ) throws AuthenticationException{
- // @RequestBody JwtAuthenticationRequest authenticationRequest
- final String token = authService.login(username,password);
-
- // Return the token
- return ResponseEntity.ok(new JwtAuthenticationResponse(token));
- }
-
- @RequestMapping(value = "/auth/refresh", method = RequestMethod.GET)
- public ResponseEntity<?> refreshAndGetAuthenticationToken(
- HttpServletRequest request) throws AuthenticationException{
- String token = request.getHeader(tokenHeader);
- String refreshedToken = authService.refresh(token);
- if(refreshedToken == null) {
- return ResponseEntity.badRequest().body(null);
- } else {
- return ResponseEntity.ok(new JwtAuthenticationResponse(refreshedToken));
- }
- }
-
- @RequestMapping(value = "${jwt.route.authentication.register}", method = RequestMethod.POST)
- public SysUser register(@RequestBody SysUser addedUser) throws AuthenticationException{
- return authService.register(addedUser);
- }
- }
接在来就是最重要的filter类:
- package org.security.config;
- import java.io.IOException;
- import javax.servlet.FilterChain;
- import javax.servlet.ServletException;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import org.security.util.JwtTokenUtil;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
- import org.springframework.security.core.context.SecurityContextHolder;
- import org.springframework.security.core.userdetails.UserDetails;
- import org.springframework.security.core.userdetails.UserDetailsService;
- import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
- import org.springframework.stereotype.Component;
- import org.springframework.web.filter.OncePerRequestFilter;
-
- @Component
- public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
-
- @Autowired
- private UserDetailsService userDetailsService;
-
- @Value("${jwt.header}")
- private String tokenHeader;
-
- @Value("${jwt.tokenHead}")
- private String tokenHead;
-
- @Autowired
- private JwtTokenUtil jwtTokenUtil;
- @Override
- protected void doFilterInternal(
- HttpServletRequest request,
- HttpServletResponse response,
- FilterChain chain) throws ServletException, IOException {
- String authHeader = request.getHeader(this.tokenHeader);
- if (authHeader != null && authHeader.startsWith(tokenHead)) {
- final String authToken = authHeader.substring(tokenHead.length()); // The part after "Bearer "
- String username = jwtTokenUtil.getUsernameFromToken(authToken);
- logger.info("checking authentication " + username);
- if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
- UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
- if (jwtTokenUtil.validateToken(authToken, userDetails)) {
- UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
- userDetails, null, userDetails.getAuthorities());
- authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(
- request));
- logger.info("authenticated user " + username + ", setting security context");
- SecurityContextHolder.getContext().setAuthentication(authentication);
- }
- }
- }
- chain.doFilter(request, response);
- }
- }
将security中配置文件注解去掉,去掉之后如下所示:
- package org.security.config;
-
- import org.security.service.CustomUserService;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.http.HttpMethod;
- import org.springframework.security.authentication.AuthenticationManager;
- import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
- import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
- import org.springframework.security.config.annotation.web.builders.HttpSecurity;
- import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
- import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
- import org.springframework.security.config.http.SessionCreationPolicy;
- import org.springframework.security.core.userdetails.UserDetailsService;
- import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
- import org.springframework.security.crypto.password.PasswordEncoder;
- import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
-
- @Configuration
- @EnableWebSecurity
- @EnableGlobalMethodSecurity(prePostEnabled = true)
- public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
- @Bean
- protected UserDetailsService customUserService() {
- return new CustomUserService();
- }
-
- @Autowired
- public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
- authenticationManagerBuilder
- // 设置UserDetailsService
- .userDetailsService(customUserService())
- // 使用BCrypt进行密码的hash
- .passwordEncoder(passwordEncoder());
- }
-
- // 装载BCrypt密码编码器
- @Bean
- public PasswordEncoder passwordEncoder() {
- return new BCryptPasswordEncoder();
- }
-
- @Bean
- public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
- return new JwtAuthenticationTokenFilter();
- }
-
- @Override
- protected void configure(AuthenticationManagerBuilder auth) throws Exception {
- auth.userDetailsService(customUserService());
- }
-
- @Bean
- @Override
- public AuthenticationManager authenticationManagerBean() throws Exception {
- return super.authenticationManagerBean();
- }
-
- @Override
- protected void configure(HttpSecurity httpSecurity) throws Exception {
- httpSecurity
- // 由于使用的是JWT,我们这里不需要csrf
- .csrf().disable()
- // 基于token,所以不需要session
- .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
- .authorizeRequests()
- .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
- // 允许对于网站静态资源的无授权访问
- .antMatchers(
- HttpMethod.GET,
- "/",
- "/*.html",
- "/favicon.ico",
- "/**/*.html",
- "/**/*.css",
- "/**/*.js"
- ).permitAll()
- // 对于获取token的rest api要允许匿名访问
- .antMatchers("/auth/**").permitAll()
- .antMatchers("/admin/**").hasIpAddress("127.0.0.1")
- .antMatchers("/admin/**").access("hasAuthority('ROLE_ADMIN')")
- // .anyRequest().authenticated().and().formLogin().loginPage("/login")
- // .failureUrl("/login?error").permitAll().and().logout().permitAll();
- // 除上面外的所有请求全部需要鉴权认证
- .anyRequest().authenticated();
- // 添加JWT filter
- httpSecurity
- .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
- // 禁用缓存
- httpSecurity.headers().cacheControl();
- }
- }
到这边jwt整合也介绍了,现在我们就可以postman来进行测试。
测试结果如下所示:
生成token:
刷新token:
不带token访问接口:
带token但是权限不够:
带token而且权限也够:
到这里关于 SpringBoot+SpringSecurity+JWT就基本结束了,这篇博客我重点是将如何通过代码来实现这个功能,关于代码本身没有很细致的介绍。关于代码功能的介绍,例如怎么通过注解配置权限控制,如何通过配置来进行权限控制,token更新和验证等等的细节,我会在我这篇博客的教学视频中介绍,视频地址:点击打开链接
GitHub地址:点击打开链接
如果大家对文章有什么问题或者疑意之类的,可以加我订阅号在上面留言,订阅号上面我会定期更新最新博客。如果嫌麻烦可以直接加我wechat:lzqcode
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。