任何应用考虑到安全,绝不能明文的方式保存密码。密码应该通过哈希算法进行加密。Spring Security提供了BCryptPasswordEncoder类,实现Spring的PasswordEncoder接口使用BCrypt强哈希方法来加密密码。BCrypt强哈希方法,每次加密的结果都不一样。
1.在tensquare_user工程的pom引入依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
2.我们在添加了spring security依赖后,所有的地址都被spring security所控制了,我们目前只是需要用到BCrypt密码加密的部分,所以我们要添加一个配置类,配置为所有地址都可以匿名访问。
/**
* 安全配置类
*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/**").permitAll()
.anyRequest().authenticated()
.and().csrf().disable();
}
}
3.在启动类中配置BCryptPasswordEncoder Bean
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
1.tensquare_user工程的 UserController 类,注入BCryptPasswordEncoder bean
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
2.修改 UserController 类的register方法,添加密码加密的逻辑
String password = bCryptPasswordEncoder.encode(user.getPassword());
user.setPassword(password);
1.编写 UserController 类的login方法:
/**
* 用户手机号登录
* @param mobile
* @param password
* @return
*/
@RequestMapping(value = "/login", method = RequestMethod.POST)
@ResponseBody
public Result login(String mobile, String password) {
//根据手机号查找用户
User user = userService.findByMobile(mobile);
//如果用户不为空并且密码匹配,则登陆成功,否则登陆失败
if (user != null && bCryptPasswordEncoder.matches(password, user.getPassword())) {
return new Result(true, StatusCode.OK, "登陆成功");
} else {
return new Result(false, StatusCode.LOGINERROR, "用户名或密码错误");
}
}
2.UserService、UserDao的findByMobile方法省略
HTTP Basic Auth简单点说明就是每次请求API时都提供用户的username和password,简言之,Basic Auth是配合RESTful API 使用的最简单的认证方式,只需提供用户名密码即可,但由于有把用户名密码暴露给第三方客户端的风险,在生产环境下被使用的越来越少。因此,在开发对外开放的RESTful API时,尽量避免采用HTTP Basic Auth。
Cookie认证机制就是为一次请求认证在服务端创建一个Session对象,同时在客户端的浏览器端创建了一个Cookie对象;通过客户端带上来Cookie对象来与服务器端的session对象匹配来实现状态管理的。默认的,当我们关闭浏览器的时候,cookie会被删除。但可以通过修改cookie 的expire time使cookie在一定时间内有效;
1.OAuth(开放授权)是一个开放的授权标准,允许用户让第三方应用访问该用户在某一web服务上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。
2.OAuth允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的网站(例如,视频编辑网站)在特定的时段(例如,接下来的2小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样,OAuth允许用户授权第三方网站访问他们存储在另外的服务提供者上的信息,而不需要分享他们的访问许可或他们数据的所有内容。
1.流程大概如下:
2.Token机制与Cookie机制相比有什么好处呢?
此处省略…请自行查阅
JWT是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。
一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。
1.头部(Header)
头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个JSON对象。
比如{ “typ”: “JWT”,“alg”: “HS256”},在这里,我们说明了这是一个JWT,并且我们所用的签名算法(后面会提到)是HS256算法。对它也要进行Base64编码,之后的字符串就成了JWT的Header(头部)。编码后的字符串如下:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
2.载荷(Payload)
载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品。
(1)标准中注册的声明:
(2)你还可以在这个部分定义私有字段,比如{“sub”:“1234567890”,“name”:“John Doe”,“admin”:true},然后在对其进行base64编码得到Jwt的第二部分。
(3)注意在JWT中,不应该在载荷里面加入任何敏感的数据—>因为Base64解码
3.签证(signature)
略
JJWT是一个提供端到端的JWT创建和验证的Java库。
1.在tensquare_common工程引入依赖:
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwtartifactId>
<version>0.9.1version>
dependency>
2.在tensquare_common工程中编写工具类。
/**
* jwt工具类
*/
@ConfigurationProperties("jwt.config")
public class JwtUtil {
private String key;
private long ttl;
//生成jwt
public String createJWT(String id, String subject, String roles) {
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
JwtBuilder builder = Jwts.builder().setId(id)
.setSubject(subject)
//设置签发时间
.setIssuedAt(now)
//设置签名密钥
.signWith(SignatureAlgorithm.HS256, key)
//自定义声明
.claim("roles", roles);
if (ttl > 0) {
//设置过期时间(并不希望签发的token是永久生效的)
builder.setExpiration(new Date(nowMillis + ttl));
}
return builder.compact();
}
//解析jwtStr
public Claims parseJWT(String jwtStr) {
Claims claims = Jwts
.parser()
.setSigningKey(key)
.parseClaimsJws(jwtStr)
.getBody();
System.out.println(claims.getId());
return claims;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public long getTtl() {
return ttl;
}
public void setTtl(long ttl) {
this.ttl = ttl;
}
}
0.在tensquare_user工程的application.properties,添加配置:
#jwt相关配置(ttl:5min)
jwt.config.key=bdsoft
jwt.config.ttl=300000
1.在启动类中配置 JwtUtil bean。
@Bean
public JwtUtil jwtUtil(){
return new JwtUtil();
}
2.修改UserController,引入JwtUtil,修改login方法,返回token。
@RequestMapping(value = "/login", method = RequestMethod.POST)
@ResponseBody
public Result login(String mobile, String password) {
//根据手机号查找用户
User user = userService.findByMobile(mobile);
//如果用户不为空并且密码匹配,则登陆成功,否则登陆失败
if (user != null && bCryptPasswordEncoder.matches(password, user.getPassword())) {
//生成token并返回
String token = jwtUtil.createJWT(user.getId(), user.getNickname(), "user");
Map map = new HashMap<>();
map.put("token", token);
map.put("nickname", user.getNickname()); //用户昵称
return new Result(true, StatusCode.OK, "登陆成功", map);
} else {
return new Result(false, StatusCode.LOGINERROR, "用户名或密码错误");
}
}
3.使用postman测试api
需求:删除用户,必须拥有管理员权限admin,否则不能删除。
1.修改tensquare_user工程UserController类的delete方法:
/**
* 删除用户
* @param id
* @return
*/
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
@ResponseBody
public Result delete(@PathVariable String id, HttpServletRequest request) {
//获取头信息
String token = request.getHeader("token");
if(token==null){
return new Result(false,StatusCode.ACCESSERROR,"权限不足");
}
Claims claims = jwtUtil.parseJWT(token);
if(claims==null || !"admin".equals(claims.get("roles"))){
return new Result(false,StatusCode.ACCESSERROR,"权限不足");
}
userService.deleteById(id);
return new Result(true, StatusCode.OK, "删除成功");
}
需求: 如果我们每个方法都去写一段代码,冗余度太高,不利于维护,那如何做使我们的代码看起来更清爽呢?我们可以将这段代码放入拦截器去实现。
1.Spring为我们提供了org.springframework.web.servlet.handler.HandlerInterceptorAdapter这个适配器,继承此类,可以非常方便的实现自己的拦截器。
他有三个方法:
分别实现预处理、后处理(调用了Service并返回ModelAndView,但未进行页面渲染)、返回处理(已经渲染了页面)
在preHandle中,可以进行编码、安全控制等处理;
在postHandle中,有机会修改ModelAndView;
在afterCompletion中,可以根据ex是否为null判断是否发生了异常,进行日志记录。
2.创建拦截器类 JwtFilter
@Component
public class JwtFilter extends HandlerInterceptorAdapter {
@Autowired
private JwtUtil jwtUtil;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle...");
//被final修饰的变量,不可变的是变量的引用,而不是变量的内容
final String token = request.getHeader("token");
if (token != null) {
Claims claims = jwtUtil.parseJWT(token);
if (claims != null) {
if ("admin".equals(claims.get("roles"))) { //如果是管理员
request.setAttribute("admin_claims", claims);
}
if ("user".equals(claims.get("roles"))) { //如果是用户
request.setAttribute("user_claims", claims);
}
}
}
return true;
}
}
3.配置拦截器
/**
* WebMvcConfigurerAdapter已经过时,新的版本解决方案目前有两种:
* 1.实现WebMvcConfigurer
* 2.继承WebMvcConfigurationSupport
*/
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
@Autowired
private JwtInterceptor jwtInterceptor;
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/**/login");
}
}
4.修改tensquare_user工程UserController类的delete方法:
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
@ResponseBody
public Result delete(@PathVariable String id, HttpServletRequest request) {
Claims claims = (Claims) request.getAttribute("admin_claims");
if (claims == null) {
return new Result(false, StatusCode.ACCESSERROR, "权限不足");
}
userService.deleteById(id);
return new Result(true, StatusCode.OK, "删除成功");
}