目标

1.Token鉴权

2.Restful API

3.Spring Security+JWT

开始

自行新建Spring Boot工程

引入相关依赖

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

User类

非常简单的用户模型,将权限集成到了用户类中。

pacage com.domain
/**
* 用户模型
*
* @author hackyo
* Created on 2017/12/3 11:53.
*/
public class User { private String id;
private String username;
private String password;
private List<String> roles; ......
省略get、set方法
...... }

IUserRepository类

需实现对用户表的增删改查,此处可采用任意数据库,具体实现自行编写。

package com.dao
/**
* 用户表操作接口
*
* @author hackyo
* Created on 2017/12/3 11:53.
*/
@Component
public interface IUserRepository{ /**
* 通过用户名查找用户
*
* @param username 用户名
* @return 用户信息
*/
User findByUsername(String username); }

JwtUser类

安全模块的用户模型

package com.security;

import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; /**
* 安全用户模型
*
* @author hackyo
* Created on 2017/12/8 9:20.
*/
public class JwtUser implements UserDetails { private String username;
private String password;
private Collection<? extends GrantedAuthority> authorities; JwtUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
this.username = username;
this.password = password;
this.authorities = authorities;
} @Override
public String getUsername() {
return username;
} @JsonIgnore
@Override
public String getPassword() {
return password;
} @Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
} @JsonIgnore
@Override
public boolean isAccountNonExpired() {
return true;
} @JsonIgnore
@Override
public boolean isAccountNonLocked() {
return true;
} @JsonIgnore
@Override
public boolean isCredentialsNonExpired() {
return true;
} @JsonIgnore
@Override
public boolean isEnabled() {
return true;
} }

JwtTokenUtil类

Token工具类

这里设置了密钥为aaaaaaaa,有效期为2592000秒

package com.security;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
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; /**
* JWT工具类
*
* @author hackyo
* Created on 2017/12/8 9:20.
*/
@Component
public class JwtTokenUtil implements Serializable { /**
* 密钥
*/
private final String secret = "aaaaaaaa"; /**
* 从数据声明生成令牌
*
* @param claims 数据声明
* @return 令牌
*/
private String generateToken(Map<String, Object> claims) {
Date expirationDate = new Date(System.currentTimeMillis() + 2592000L * 1000);
return Jwts.builder().setClaims(claims).setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, secret).compact();
} /**
* 从令牌中获取数据声明
*
* @param token 令牌
* @return 数据声明
*/
private Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
} catch (Exception e) {
claims = null;
}
return claims;
} /**
* 生成令牌
*
* @param userDetails 用户
* @return 令牌
*/
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>(2);
claims.put("sub", userDetails.getUsername());
claims.put("created", new Date());
return generateToken(claims);
} /**
* 从令牌中获取用户名
*
* @param token 令牌
* @return 用户名
*/
public String getUsernameFromToken(String token) {
String username;
try {
Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
}
return username;
} /**
* 判断令牌是否过期
*
* @param token 令牌
* @return 是否过期
*/
public Boolean isTokenExpired(String token) {
try {
Claims claims = getClaimsFromToken(token);
Date expiration = claims.getExpiration();
return expiration.before(new Date());
} catch (Exception e) {
return false;
}
} /**
* 刷新令牌
*
* @param token 原令牌
* @return 新令牌
*/
public String refreshToken(String token) {
String refreshedToken;
try {
Claims claims = getClaimsFromToken(token);
claims.put("created", new Date());
refreshedToken = generateToken(claims);
} catch (Exception e) {
refreshedToken = null;
}
return refreshedToken;
} /**
* 验证令牌
*
* @param token 令牌
* @param userDetails 用户
* @return 是否有效
*/
public Boolean validateToken(String token, UserDetails userDetails) {
JwtUser user = (JwtUser) userDetails;
String username = getUsernameFromToken(token);
return (username.equals(user.getUsername()) && !isTokenExpired(token));
} }

JwtUserDetailsServiceImpl类

用户验证方法类

package com.security;

import com.safepass.dao.IUserRepository;
import com.safepass.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service; import java.util.stream.Collectors; /**
* 用户验证方法
*
* @author hackyo
* Created on 2017/12/8 9:18.
*/
@Service
public class JwtUserDetailsServiceImpl implements UserDetailsService { private IUserRepository userRepository; @Autowired
public JwtUserDetailsServiceImpl(IUserRepository userRepository) {
this.userRepository = userRepository;
} @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException(String.format("No user found with username '%s'.", username));
} else {
return new JwtUser(user.getUsername(), user.getPassword(), user.getRoles().stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()));
}
} }

JwtAuthenticationTokenFilter类

Token过滤器实现

package com.security;

import org.springframework.beans.factory.annotation.Autowired;
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; import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException; /**
* Token过滤器
*
* @author hackyo
* Created on 2017/12/8 9:28.
*/
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { private UserDetailsService userDetailsService;
private JwtTokenUtil jwtTokenUtil; @Autowired
public JwtAuthenticationTokenFilter(UserDetailsService userDetailsService, JwtTokenUtil jwtTokenUtil) {
this.userDetailsService = userDetailsService;
this.jwtTokenUtil = jwtTokenUtil;
} @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
String tokenHead = "Bearer ";
if (authHeader != null && authHeader.startsWith(tokenHead)) {
String authToken = authHeader.substring(tokenHead.length());
String username = jwtTokenUtil.getUsernameFromToken(authToken);
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));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
chain.doFilter(request, response);
} }

EntryPointUnauthorizedHandler类

自定义了身份验证失败的返回值

package com.security;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; /**
* 自定401返回值
*
* @author hackyo
* Created on 2017/12/9 20:10.
*/
@Component
public class EntryPointUnauthorizedHandler implements AuthenticationEntryPoint { @Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setStatus(401);
} }

RestAccessDeniedHandler类

自定了权限不足的返回值

package com.security;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException; /**
* 自定403返回值
*
* @author hackyo
* Created on 2017/12/9 20:10.
*/
@Component
public class RestAccessDeniedHandler implements AccessDeniedHandler { @Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setStatus(403);
} }

WebSecurityConfig类

安全配置类

这里设置了禁止访问所有地址,除了用于验证身份的/user/**地址

同时密码的加密方式为BCrypt

package com.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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; /**
* 安全模块配置
*
* @author hackyo
* Created on 2017/12/8 9:15.
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { private UserDetailsService userDetailsService;
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
private EntryPointUnauthorizedHandler entryPointUnauthorizedHandler;
private RestAccessDeniedHandler restAccessDeniedHandler;
private PasswordEncoder passwordEncoder; @Autowired
public WebSecurityConfig(UserDetailsService userDetailsService, JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter, EntryPointUnauthorizedHandler entryPointUnauthorizedHandler, RestAccessDeniedHandler restAccessDeniedHandler) {
this.userDetailsService = userDetailsService;
this.jwtAuthenticationTokenFilter = jwtAuthenticationTokenFilter;
this.entryPointUnauthorizedHandler = entryPointUnauthorizedHandler;
this.restAccessDeniedHandler = restAccessDeniedHandler;
this.passwordEncoder = new BCryptPasswordEncoder();
} @Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(this.userDetailsService).passwordEncoder(passwordEncoder);
} @Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/user/**").permitAll()
.anyRequest().authenticated()
.and().headers().cacheControl();
httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
httpSecurity.exceptionHandling().authenticationEntryPoint(entryPointUnauthorizedHandler).accessDeniedHandler(restAccessDeniedHandler); } }

IUserService类

定义用户的基本操作

package com.service;

import com.domain.User;

/**
* 用户操作接口
*
* @author hackyo
* Created on 2017/12/3 11:53.
*/
public interface IUserService { /**
* 用户登录
*
* @param username 用户名
* @param password 密码
* @return 操作结果
*/
String login(String username, String password); /**
* 用户注册
*
* @param user 用户信息
* @return 操作结果
*/
String register(User user); /**
* 刷新密钥
*
* @param oldToken 原密钥
* @return 新密钥
*/
String refreshToken(String oldToken); }

UserServiceImpl类

IUserService的实现类,注册时会将用户权限设置为ROLE_USER,同时将密码使用BCrypt加密

package com.service.impl;

import com.dao.IUserRepository;
import com.domain.User;
import com.security.JwtTokenUtil;
import com.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
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.ArrayList;
import java.util.List; /**
* 用户操作接口实现
*
* @author hackyo
* Created on 2017/12/3 11:53.
*/
@Service
public class UserServiceImpl implements IUserService { private AuthenticationManager authenticationManager;
private UserDetailsService userDetailsService;
private JwtTokenUtil jwtTokenUtil;
private IUserRepository userRepository; @Autowired
public UserServiceImpl(AuthenticationManager authenticationManager, UserDetailsService userDetailsService, JwtTokenUtil jwtTokenUtil, IUserRepository userRepository) {
this.authenticationManager = authenticationManager;
this.userDetailsService = userDetailsService;
this.jwtTokenUtil = jwtTokenUtil;
this.userRepository = userRepository;
} @Override
public String login(String username, String password) {
UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(username, password);
Authentication authentication = authenticationManager.authenticate(upToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
return jwtTokenUtil.generateToken(userDetails);
} @Override
public String register(User user) {
String username = user.getUsername();
if (userRepository.findByUsername(username) != null) {
return "用户已存在";
}
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String rawPassword = user.getPassword();
user.setPassword(encoder.encode(rawPassword));
List<String> roles = new ArrayList<>();
roles.add("ROLE_USER");
user.setRoles(roles);
userRepository.insert(user);
return "success";
} @Override
public String refreshToken(String oldToken) {
String token = oldToken.substring("Bearer ".length());
if (!jwtTokenUtil.isTokenExpired(token)) {
return jwtTokenUtil.refreshToken(token);
}
return "error";
} }

UserController类

控制器,控制访问

package com.controller;

import com.domain.User;
import com.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.AuthenticationException;
import org.springframework.web.bind.annotation.*; /**
* 用户管理Controller
*
* @author hackyo
* Created on 2017/12/3 11:53.
*/
@CrossOrigin
@RestController
@RequestMapping(value = "/user", produces = "text/html;charset=UTF-8")
public class UserController { private IUserService userService; @Autowired
public UserController(IUserService userService) {
this.userService = userService;
} /**
* 用户登录
*
* @param username 用户名
* @param password 密码
* @return 操作结果
* @throws AuthenticationException 错误信息
*/
@PostMapping(value = "/login", params = {"username", "password"})
public String getToken(String username, String password) throws AuthenticationException {
return userService.login(username, password);
} /**
* 用户注册
*
* @param user 用户信息
* @return 操作结果
* @throws AuthenticationException 错误信息
*/
@PostMapping(value = "/register")
public String register(User user) throws AuthenticationException {
return userService.register(user);
} /**
* 刷新密钥
*
* @param authorization 原密钥
* @return 新密钥
* @throws AuthenticationException 错误信息
*/
@GetMapping(value = "/refreshToken")
public String refreshToken(@RequestHeader String authorization) throws AuthenticationException {
return userService.refreshToken(authorization);
} }

 

使用

只需要在方法或类上加注解即可实现账号控制

例如,我们想控制该方法只允许用户本人使用,#号表示方法的参数,可以在参数中加上@P('name')来指定名称,同时也可直接使用模型,如user.username等

总之,其中可以写入任何Spring EL

@PreAuthorize("#username == authentication.name")
@GetMapping(value = "/getInfo")
public String getInfo(String username) {
return JSON.toJSONString(userService.getInfo(username));
}

另外也可以自定义控制注解,使用@PostFilter注解,并实现hasPermission类即可,同时需要在WebSecurityConfigurerAdapter中开启。

测试

运行程序后,我们使用Postman进行测试

1.注册

URL:http://localhost:8080/user/register

参数:username、password

返回success即为成功

 2.登录

URL:http://localhost:8080/user/login

参数:username、password

可以看到服务器将我们的Token返回了

 3.刷新Token

URL(GET方法):http://localhost:8080/user/refreshToken

参数:在Header中加入登录时返回的Token,注意,需要在Token前加上“Bearer ”,最后有个空格

Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE1MTMzMTE1NjMsInN1YiI6IjEyMyIsImNyZWF0ZWQiOjE1MTI3MDY3NjM3NjB9.baiY8QcbJgq4FQMC2piN1smbW57WjDDTiRVIL9hJeC_DcPgcyJweWqkS6g7825mPKFlByuUx7XN8nUOIszDVcw

可以看到服务器给我们返回了新的Token,如果我们不加上Token的话,将无法访问

参考:

  http://www.jianshu.com/p/6307c89fe3fa

  http://www.jianshu.com/p/4468a2fff879

Spring Boot中使用使用Spring Security和JWT的更多相关文章

  1. Spring Boot中使用Spring Security进行安全控制

    我们在编写Web应用时,经常需要对页面做一些安全控制,比如:对于没有访问权限的用户需要转到登录表单页面.要实现访问控制的方法多种多样,可以通过Aop.拦截器实现,也可以通过框架实现(如:Apache ...

  2. Spring Boot中的静态资源文件

    Spring Boot中的静态资源文件 1.SSM中的配置 2.Spring Boot 中的配置 2.1 整体规划 2.2 源码解读 2.3 自定义配置 2.3.1 application.prope ...

  3. Spring Boot中使用 Spring Security 构建权限系统

    Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架.它提供了一组可以在Spring应用上下文中配置的Bean,为应用系统提供声明式的安全 ...

  4. Spring Boot中集成Spring Security 专题

    check to see if spring security is applied that the appropriate resources are permitted: @Configurat ...

  5. Spring Boot 中使用 Spring Security, OAuth2 跨域问题 (自己挖的坑)

    使用 Spring Boot 开发 API 使用 Spring Security + OAuth2 + JWT 鉴权,已经在 Controller 配置允许跨域: @RestController @C ...

  6. Spring Boot中Starter是什么

    比如我们要在Spring Boot中引入Web MVC的支持时,我们通常会引入这个模块spring-boot-starter-web,而这个模块如果解压包出来会发现里面什么都没有,只定义了一些POM依 ...

  7. 在spring boot中使用webSocket组件(二)

    该篇演示如何使用websocket创建一对一的聊天室,废话不多说,我们马上开始! 一.首先先创建前端页面,代码如下图所示: 1.login.html <!DOCTYPE html> < ...

  8. Spring Boot 中的同一个 Bug,竟然把我坑了两次!

    真是郁闷,不过这事又一次提醒我解决问题还是要根治,不能囫囵吞枣,否则相同的问题可能会以不同的形式出现,每次都得花时间去搞.刨根问底,一步到位,再遇到类似问题就可以分分钟解决了. 如果大家没看过松哥之前 ...

  9. spring boot rest 接口集成 spring security(2) - JWT配置

    Spring Boot 集成教程 Spring Boot 介绍 Spring Boot 开发环境搭建(Eclipse) Spring Boot Hello World (restful接口)例子 sp ...

随机推荐

  1. ecplise properties文件 中文转码

    1.安装插件 2.重开ecplise 3.在项目的乱码文件如jeesite.properties右键 openwith propertiesEditor 就可以看到中文了 输入 proedit 安装完 ...

  2. PHP require php > 5.3.0

    项目版本要求 在5.3版本以上,如果是用 phpStudy 环境,那么直接切换版本即可

  3. MT【273】2014新课标压轴题之$\ln2$的估计

    已知函数$f(x)=e^x-e^{-x}-2x$(1)讨论$f(x)$的单调性;(2)设$g(x)=f(2x)-4bf(x),$当$x>0$时,$g(x)>0,$求$b$的最大值;(3)已 ...

  4. MT【244】调和分割

    已知椭圆方程:$\dfrac{x^2}{4}+\dfrac{y^2}{3}=1$,过点$P(1,1)$的两条直线分别与椭圆交于点$A,C$和$B,D$,且满足$\overrightarrow{AP}= ...

  5. Centos7安装OpenDCIM-19.01步骤

    Centos7安装OpenDCIM-19.01步骤 openDCIM是一款免费的开源解决方案,用于管理数据中心基础设施.它已经被几家企业组织所使用,由于开发人员的不懈努力,正在迅速完善. openDC ...

  6. 【转】c语言中的#号和##号的作用

    @2019-01-25 [小记] c语言中的#号和##号的作用

  7. post提交的数据几种编码格式

    1.背景介绍 HTTP/1.1 协议规定的 HTTP 请求方法有 OPTIONS.GET.HEAD.POST.PUT.DELETE.TRACE.CONNECT 这几种.其中 POST 一般用来向服务端 ...

  8. js中的arguments用法

    //arguments对象并不是一个数组,但是访问单个参数的方式与访问数组元素的方式相同 function show(){ console.log(arguments); //arguments.pu ...

  9. Vim在图形环境下全屏产生黑边

    在终端中运行Vim或运行GVim都会遇到这个问题,当窗口全屏时,左右和底部可能会出现边框,这个边框在终端中的Vim表现为Terminal的背景颜色.下图为SpaceVim+Neovim+Termina ...

  10. 洛谷P4769 冒泡排序

    n <= 60w,∑n <= 200w,1s. 解:首先有个全排列 + 树状数组的暴力. 然后有个没有任何规律的状压...首先我想的是按照大小顺序来放数,可以分为确定绝对位置和相对位置两种 ...