重拾后端之Spring Boot(一):REST API的搭建可以这样简单
重拾后端之Spring Boot(二):MongoDb的无缝集成
重拾后端之Spring Boot(三):找回熟悉的Controller,Service
重拾后端之Spring Boot(四):使用 JWT 和 Spring Security 保护 REST API
重拾后端之Spring Boot(五):跨域、自定义查询及分页
重拾后端之Spring Boot(六):热加载、容器和多项目
通常情况下,把API直接暴露出去是风险很大的,不说别的,直接被机器攻击就喝一壶的。那么一般来说,对API要划分出一定的权限级别,然后做一个用户的鉴权,依据鉴权结果给予用户开放对应的API。目前,比较主流的方案有几种:
- 用户名和密码鉴权,使用Session保存用户鉴权结果。
- 使用OAuth进行鉴权(其实OAuth也是一种基于Token的鉴权,只是没有规定Token的生成方式)
- 自行采用Token进行鉴权
第一种就不介绍了,由于依赖Session来维护状态,也不太适合移动时代,新的项目就不要采用了。第二种OAuth的方案和JWT都是基于Token的,但OAuth其实对于不做开放平台的公司有些过于复杂。我们主要介绍第三种:JWT。
什么是JWT?
JWT是 Json Web Token
的缩写。它是基于 RFC 7519 标准定义的一种可以安全传输的 小巧 和 自包含 的JSON对象。由于数据是使用数字签名的,所以是可信任的和安全的。JWT可以使用HMAC算法对secret进行加密或者使用RSA的公钥私钥对来进行签名。
JWT的工作流程
下面是一个JWT的工作流程图。模拟一下实际的流程是这样的(假设受保护的API在/protected
中)
- 用户导航到登录页,输入用户名、密码,进行登录
- 服务器验证登录鉴权,如果改用户合法,根据用户的信息和服务器的规则生成JWT Token
- 服务器将该token以json形式返回(不一定要json形式,这里说的是一种常见的做法)
- 用户得到token,存在localStorage、cookie或其它数据存储形式中。
- 以后用户请求
/protected
中的API时,在请求的header中加入Authorization: Bearer xxxx(token)
。此处注意token之前有一个7字符长度的Bearer
- 服务器端对此token进行检验,如果合法就解析其中内容,根据其拥有的权限和自己的业务逻辑给出对应的响应结果。
- 用户取得结果

网友评论
后报403{
"timestamp": 1533267174614,
"status": 403,
"error": "Forbidden",
"message": "Access Denied",
"path": "/user-system/users"
}
调试拥有ADMIN角色。。。。
请问大佬这是什么原因
Wisdom RESTClient
https://github.com/Wisdom-Projects/rest-client
这个应该做不到,权限信息必须查询数据库的吧?不可能都放入jwt中,会使jwt长度过长而且如果更改了权限,token是无法更新的
@JsonIgnore
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
可是我写成了:
@JsonIgnore
@Override
public String getPassword() {
return null;
}
@Override
public String getUsername() {
return null;
}
@service
class JWTTokenUtils {
val logger: Logger = LogManager.getLogger(JWTTokenUtils::class.java.name)
@VALUE("\${jwt.secret}")
private var secret: String? = null
@VALUE("\${jwt.access_token.expiration}")
private var access_token_expiration: Long? = null
@VALUE("\${jwt.refresh_token.expiration}")
private var refresh_token_expiration: Long? = null
```
为什么拿不到 application.yml 里面的值呢?
···
jwt:
secret: "xxx"
tokenHead: "Bearer "
header: "Authorization"
access_token:
expiration: 60
refresh_token:
expiration: 60
···
"timestamp": 1511784602501,
"status": 401,
"error": "Unauthorized",
"exception": "org.springframework.security.authentication.InternalAuthenticationServiceException",
"message": "Unauthorized",
"path": "/auth"
}
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:84) ~[spring-security-core-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:233) ~[spring-security-core-4.2.3.RELEASE.jar:4.2.3.RELEASE]......
是为什么啊?步骤都是按照您上面一步步做的。
有一个小疑问就是token的refresh是在什么场景下使用的。
http://localhost:8999/auth/register?username=wangyi&password=1517&email=1517@QQ.com
{
"timestamp": 1504100483086,
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.http.converter.HttpMessageNotReadableException",
"message": "Required request body is missing: public com.appo.attendancesys.apiauth.user.User com.appo.attendancesys.apiauth.auth.AuthController.register(com.appo.attendancesys.apiauth.user.User) throws org.springframework.security.core.AuthenticationException",
"path": "/auth/register"
}
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.security.authentication.AuthenticationManager' available:
@Autowired
public AuthServiceImpl(
AuthenticationManager authenticationManager,
UserDetailsService userDetailsService,
JwtTokenUtil jwtTokenUtil,
UserRepository userRepository) {
this.authenticationManager = authenticationManager;
this.userDetailsService = userDetailsService;
this.jwtTokenUtil = jwtTokenUtil;
this.userRepository = userRepository;
}
{
"timestamp": 1502240238418,
"status": 401,
"error": "Unauthorized",
"exception": "org.springframework.security.authentication.InternalAuthenticationServiceException",
"message": "Unauthorized",
"path": "/auth/"
}
感觉像是 在
final Authentication authentication = authenticationManager.authenticate(upToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
这一步出了问题,但是没有报任何错误,请大神指教,感谢!
spring.jackson.serialization.INDENT_OUTPUT=true
spring.data.mongodb.host=localhost
spring.data.mongodb.port=27017
spring.data.mongodb.database=springboot
# jwt
jwt.header=Authorization
jwt.secret=mySecret
jwt.expiration=604800
jwt.tokenHead=Bearer
jwt.route.authentication.path=auth
jwt.route.authentication.refresh=refresh
jwt.route.authentication.register=auth/register
# logging
logging.level.org.springframework.data=DEBUG
logging.level.org.springframework.security=DEBUG
public String login(String username, String password) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken=new UsernamePasswordAuthenticationToken(username,password);
Authentication authentication=authenticationManager.authenticate(usernamePasswordAuthenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetails userDetails= userDetailsService.loadUserByUsername(username);
String token = jwtTokenUtil.generateToken(userDetails);
return token;
}
获得token时postman显示、
{
"timestamp": 1501205673323,
"status": 403,
"error": "Forbidden",
"exception": "org.springframework.security.authentication.InternalAuthenticationServiceException",
"message": "Access Denied",
"path": "/auth"
}
没有任何错误信息
然后我打断点看发现这句有问题,但是我不清楚什么原因
Authentication authentication=authenticationManager.authenticate(usernamePasswordAuthenticationToken);
2017-07-13 19:50:47.051 DEBUG 13164 --- [nio-9000-exec-1] o.s.s.w.a.i.FilterSecurityInterceptor : Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@9055c2bc: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
2017-07-13 19:50:47.054 DEBUG 13164 --- [nio-9000-exec-1] o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.web.access.expression.WebExpressionVoter@7f7a76a5, returned: -1
2017-07-13 19:50:47.057 DEBUG 13164 --- [nio-9000-exec-1] o.s.s.w.a.ExceptionTranslationFilter : Access is denied (user is anonymous); redirecting to authentication entry point
求解啊
这个代码用postman是没有任何问题的,但是如果加上跨域+ajax请求,浏览器报错,打断点发现问题出在JwtAuthenticationTokenFilter这里,拦截器根本拦截不到Authorization这个Header,postman就可以...
度娘得知,前端这种跨域请求是两次查询,第一次是Option,服务端返回状态给前端,前端才会据此发起第二次真实请求。自己改了下代码,斗胆贴一下:
```
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
//设置response,否则前端页面拿不到option结果
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json;charset=utf-8");
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
response.setHeader("Access-Control-Allow-Headers","Authorization");
String method = request.getMethod();
if(method.equals("OPTIONS")){
//预请求需要往回写 让ajax预请求知道预请求是成功的
response.setStatus(202);
response.getWriter().write(1);
}
else{
//取得header
String authHeader = request.getHeader(this.tokenHeader);
*****以下为原本内容
```
spring security4视频教程
@Override
public String login(String username, String password) {
UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(username, password);
final Authentication authentication = authenticationManager.authenticate(upToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
final UserDetails userDetails = userDetailsService.loadUserByUsername(username);
final String token = jwtTokenUtil.generateToken(userDetails);
return token;
}
这一段中,
UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(username, password);
final Authentication authentication = authenticationManager.authenticate(upToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
这一部分的作用是什么呢?
我看也没有验证用户名密码的正确性,那岂不是什么密码都可以获取token了么?
谢谢啦
io.jsonwebtoken.MalformedJwtException: Unable to read JSON value: �����!L��ȉ
这个是异常信息,在 getClaimsFromToken 方法。
1、客户端请求登录--->验证通过---->服务端生成token发送给客户端 前 服务端是否要保存?
2、客户端带token再次请求,服务端获取客户端的token解密后,服务端根据什么依据对比?
3、客户端登录验证后,得到token,希望在没退出、浏览器没关闭前一直有效,怎么做?
其它三个问题应该算是一个了!
另,我觉得你应该考虑出本书了,认真的