Swagger+Spring开发Rest接口
流程
- 1.利用Web工具
Restlet Studio
写Swagger文档 - 2.利用
SwaggerEditor
生成Spring服务端代码 - 3.在代码中整合
SpringDataJPA
- 4.在代码中整合
JsonWebToken
- 5.在代码中整合
SpringSecurity
写Swagger文档
使用图形化web工具restlet studio
比较重要的步骤为:
1.创建Data Type
对应的是数据库实体类,或者Rest请求的交互数据模型,请求体和返回体内容尽量不要在接口中设置多个String
参数,而是事先创建Data Type
2.设置全局安全策略
这里我们用自定义的JWT所以选Custom类型
3.设置接口安全策略
需要设置为继承自全局。同时还要设置Header
4.添加资源方法
根据Rest规范Post
Delete
Put
Get
四个方法对应增删改查。其中Get
和Delete
的Url应该是/resource/{id}
。而Put
和Post
则是/resource
传json参数
5.简化方法
在SpringDataJPA
中,存和改都是save
方法,我们开发的时候可以做简化,将Put
方法归并到Post
。另外我们还会有获取资源列表的接口。所以一般如下
6.获取文档
生成代码
在http://editor.swagger.io
中生成Spring
服务端代码,生成的就是SpringBoot格式的代码。
SpringDataJPA
Entity:生成的代码主要有俩文件夹,一个是model一个是api,我们在model中将实体类和数据库做关联。添加如下注解,其中@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
一定不要忘了,这是在转换json的时候将其加到json的一个字段里了,我们不需要所以忽略掉这些属性。
@Entity
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
@Table(name = "device")
public class Device {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(nullable = false)
@JsonProperty("devid")
private Integer devid = null;
}
Repository:新建一个Repository包,为实体类创建JpaRepository<Entity,PrimaryKeyType>
public interface DeviceDao extends JpaRepository<Device, Integer> {}
JsonWebToken
创建 Utils
包来放工具类,例如我们借助jjwt
的包封装JWTHelper
类,下面是依赖和代码:
pom.xml
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
JwtHelper.java
@Component
public class JwtHelper {
@Value("${jwt.secret}")
private String stringKey;
@Value("${jwt.expiration}")
private long ttlMinute;
public String createJWT( String subject){
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256 ;
long nowMillis = System.currentTimeMillis();
long ttlMillis = ttlMinute*1000*60;
Date now = new Date( nowMillis);
SecretKey key = generalKey();
JwtBuilder builder = Jwts.builder()
.setIssuedAt(now)
.setSubject(subject)
.signWith(signatureAlgorithm, key);
if (ttlMillis >= 0){
long expMillis = nowMillis + ttlMillis;
Date exp = new Date( expMillis);
builder.setExpiration( exp);
}
return builder.compact();
}
public String parseJWT(String jwt){
SecretKey key = generalKey();
try{
Claims claims = Jwts.parser()
.setSigningKey( key)
.parseClaimsJws(jwt).getBody();
return claims.getSubject();
}catch (Exception e){
return null;
}
}
private SecretKey generalKey(){
byte[] encodedKey = Base64.decodeBase64(stringKey);
SecretKey key =
new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
}
注:jjwt的功能较多,这里只利用了subject
和expire
,subject
文本可以加密并且有存活时间,解密可以获取文本原文,如果超时了或者无法解密就返回null
。两个public方法createJWT
parseJWT
就是加密和解密方法。
在/login
中产生JsonWebToken
:
@Controller
public class LoginApiController implements LoginApi {
@Autowired
UserDao userDao;
@Autowired
JwtHelper jwtHelper;
private final Log logger = LogFactory.getLog(this.getClass());
public ResponseEntity<Token> loginPost(@ApiParam(value = "" ,required=true ) @Valid @RequestBody Login login) {
Token token = new Token();
User user= userDao.getUserByUsername(login.getUsername());
if(user.getPassword().equals(login.getPassword())){
ObjectMapper mapper = new ObjectMapper();
try {
logger.info("write json to token:"+
mapper.writeValueAsString(user));
user.setPassword("");//密码在token中没有用,且可能位数太长影响加解密速度
token.setAccessToken(
jwtHelper.createJWT(mapper.writeValueAsString(user))
);
return new ResponseEntity<Token>(token,HttpStatus.OK);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
return new ResponseEntity<Token>(HttpStatus.UNAUTHORIZED);
}
}
SpringSecurity
除了Authentication
我们还需要Authorization
,需要一个权限框架。
过滤器和安全设置
jwt过滤器:
@Component
public class JwtFilter extends OncePerRequestFilter {
private final Log logger = LogFactory.getLog(this.getClass());
@Autowired
private JwtHelper JwtHelper;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
String authstr = request.getHeader("Authorization");
if(authstr==null||!authstr.startsWith("Bearer ")){
chain.doFilter(request, response);
return;
}
String auth = authstr.substring(7);
logger.info("auth:"+auth);
String json = JwtHelper.parseJWT(auth);
if (json!= null) {
ObjectMapper mapper = new ObjectMapper();
UserDetails userDetails=mapper.readValue(json, User.class);
logger.info("get json from token:"+json);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
}
这样在application.properties中设置密钥和过时时间(分钟)
jwt.secret=mesh
jwt.expiration=60
另外上面用到了UserDetails接口类User
需要事先创建,且需要添加注解:
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler","accountNonLocked","credentialsNonExpired","enabled","authorities","accountNonExpired",})
为了之后Jwt加解密的Subject直接用User的json字符串,加密时直接转成json后加密,解密直接将json字符转成对象。方便~
跨域过滤器[替换swagger生成的]
@Component
public class ApiOriginFilter extends OncePerRequestFilter {
@Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse res = (HttpServletResponse) response;
res.addHeader("Access-Control-Allow-Origin", "*");
res.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS");
res.addHeader("Access-Control-Allow-Headers", "Content-Type");
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
web安全设置
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConf extends WebSecurityConfigurerAdapter{
@Autowired
JwtFilter jwtFilter;
@Autowired
ApiOriginFilter originFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
//rest接口中csrf session不需要
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
//以下url允许任何人访问
.antMatchers("/login","/api-docs","/h2-console/**","/swagger-resources/**","/**/*.html").permitAll()
//其他的请求get方法user和admin都可以访问,但post和delete需admin
.antMatchers(HttpMethod.GET).hasAnyRole("USER","ADMIN")
.antMatchers(HttpMethod.DELETE).hasRole("ADMIN")
.antMatchers(HttpMethod.POST).hasRole("ADMIN")
.antMatchers(HttpMethod.OPTIONS).permitAll()
.anyRequest().authenticated()
.and()
//关了同源限制,为了能在无session时使用h2-console
.headers().frameOptions().disable();
//在SpringSecurity权限过滤器前加我们的jwtFilter
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
//在更前面添加跨域过滤器
http.addFilterBefore(originFilter, JwtFilter.class);
}
}