1、postman接口测试工具
2、认证接口
3、shiro权限框架
(1)Postman是一款功能强大的网页调试与发送网页HTTP请求的Chrome插件。postman适用于不同的操作系统,Postman Mac、Windows X32、Windows X64、Linux系统,还支持postman 浏览器扩展程序、postman chrome应用程序等。下载地址:https://www.postman.com/downloads/
(2)collection在postman里面相当于一个文件夹,可以把同一个项目的请求放在一个Collection里方便管理和分享,Collection里面也可以再建文件夹。这里我们做一个普通接口的简单的示例
(1)basic auth认证
使用postman创建一个接口调用,请求URL:https://postman-echo.com/basic-auth
用户名:postman
密码:password
授权协议:Basic auth
Authorization:用于需要认证的接口。
Basic Auth:最基本的一种认证类型,还有OAuth 1.0/2.0、Digest Auth等认证类型。
Username/Password:这是针对Basic Auth类型的认证的用户名/密码,并非我们认为的系统登录的用户名密码。
(2)在项目中加入添加依赖也可以实现basic auth认证
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
然后访问页面时会跳转到认证页面,用户名默认是user,密码自动生成显示在控制台。
(3)bearer token认证
为了验证使用者的身份,需要客户端向服务器端提供一个可靠的验证信息,称为Token,这个token通常由Json数据格式组成,通过hash散列算法生成一个字符串,所以称为Json Web Token(Json表示令牌的原始值是一个Json格式的数据,web表示是在互联网传播的,token表示令牌,简称JWT)。
测试带token的接口,首先要进行登录,登录成功会有个token信息,向api接口发送请求的时候必须在header中放入这个token,故需要做2次请求。
(4)Springboot 使用JWT 生成token
JWT意思是Json web token,通过POST参数或者在HTTP header发送,然后进行验证,验证通过之后,就能返回响应的资源给浏览器。通常和权限框架(如shiro)搭配使用。
1.引入依赖
<dependency>
<groupId>com.auth0groupId>
<artifactId>java-jwtartifactId>
<version>3.4.0version>
dependency>
2.在annotation包下自定义注解
用来跳过验证的@PassToken
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
boolean required() default true;
}
需要登录才能进行操作的注解UserLoginToken
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {
boolean required() default true;
}
3.封装生成Token的工具类
public class JWTUtils {
public static String getToken(String userCode, String userPassword) {
return JWT.create()
.withAudience(userCode)
.sign(Algorithm.HMAC256(userPassword));
}
}
4.新增一个拦截器AuthenticationInterceptor
public class AuthenticationInterceptor extends HandlerInterceptorAdapter {
@Resource
private UserMapper userMapper;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("token");// 从 http 请求头中取出 token
// 如果不是映射到方法直接通过
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
//检查是否有passtoken注释,有则跳过认证
if (method.isAnnotationPresent(PassToken.class)) {
PassToken passToken = method.getAnnotation(PassToken.class);
if (passToken.required()) {
return true;
}
}
//检查有没有需要用户权限的注解
if (method.isAnnotationPresent(UserLoginToken.class)) {
UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
if (userLoginToken.required()) {
// 执行认证
if (token == null) {
throw new RuntimeException("无token,请重新登录");
}
// 获取 token 中的 user id
String userCode;
try {
userCode = JWT.decode(token).getAudience().get(0);
} catch (JWTDecodeException j) {
throw new RuntimeException("token验证失败");
}
User user = userMapper.selectUserByUserCode(userCode);
if (user == null) {
throw new RuntimeException("用户不存在,请重新登录");
}
// 验证 token
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getUserPassword())).build();
try {
jwtVerifier.verify(token);
} catch (JWTVerificationException e) {
throw new RuntimeException("token验证失败");
}
return true;
}
}
return true;
}
}
5.配置拦截器
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authenticationInterceptor())
.addPathPatterns("/**");
}
@Bean
public AuthenticationInterceptor authenticationInterceptor(){
return new AuthenticationInterceptor();
}
}
6.验证
不加注解的话默认不验证,登录接口一般是不验证的。在/hello中加上了登录注解,说明该接口必须登录获取token后,在请求头中加上token并通过验证才可以访问
@RestController
public class UserController {
@Autowired
private UserMapper userMapper;
@PassToken
@RequestMapping("/login")
public Object login(@RequestParam("userCode")String userCode,
@RequestParam("userPassword")String userPassword){
User user = userMapper.selectUserByUserCode(userCode);
Map map = new HashMap();
if(user.getUserPassword().equals(userPassword)){
map.put("status",1);
map.put("msg","success");
map.put("token", JWTUtils.getToken(userCode,userPassword));
}else{
map.put("status",0);
map.put("msg","error");
}
return map;
}
@UserLoginToken
@RequestMapping("/hello")
public Object hello(){
Map map = new HashMap();
map.put("name","郭靖");
map.put("birthday",new Date());
List<User> userList = userMapper.getUserList();
map.put("userlist",userList);
return map;
}
}
(1)shiro是一个权限框架,用来进行javaSE和javaEE中的权限操作。原理是通过Filter来对资源进行拦截。比Spring security更简单易用,更主流。
添加依赖
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-springartifactId>
<version>1.7.1version>
dependency>
springboot中集成shiro需要两个类,shiroConfig类和CustomRealm类。
ShiroConfig类:对shiro的一些配置,相对于之前的xml配置。包括:过滤的文件和权限,密码加密的算法,其用注解等相关功能。
@Configuration
public class ShiroConfig {
//开启AOP
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
defaultAAP.setProxyTargetClass(true);
return defaultAAP;
}
//将自己的验证方式加入容器
@Bean
public CustomRealm myShiroRealm() {
return new CustomRealm();
}
//权限管理,配置主要是Realm的管理认证
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
return securityManager;
}
//Filter工厂,设置对应的过滤条件和跳转条件
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> map = new HashMap<>();
//登出
map.put("/logout", "logout");
//对所有用户认证
map.put("/**", "authc");
//登录
shiroFilterFactoryBean.setLoginUrl("/login");
//首页
shiroFilterFactoryBean.setSuccessUrl("/index");
//错误页面,认证不通过跳转
shiroFilterFactoryBean.setUnauthorizedUrl("/error");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
//注入权限管理
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
CustomRealm类:自定义的CustomRealm继承AuthorizingRealm。并且重写父类中的doGetAuthorizationInfo(权限相关)、doGetAuthenticationInfo(身份认证)这两个方法。
public class CustomRealm extends AuthorizingRealm {
@Autowired
private LoginService loginService;
/**
* @MethodName doGetAuthorizationInfo
* @Description 权限配置类
* @Param [principalCollection]
* @Return AuthorizationInfo
* @Author hisoft
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取登录用户名
String name = (String) principalCollection.getPrimaryPrincipal();
//查询用户名称
User user = loginService.getUserByName(name);
//添加角色和权限
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
for (Role role : user.getRoles()) {
//添加角色
simpleAuthorizationInfo.addRole(role.getRoleName());
//添加权限
for (Permissions permissions : role.getPermissions()) {
simpleAuthorizationInfo.addStringPermission(permissions.getPermissionsName());
}
}
return simpleAuthorizationInfo;
}
/**
* @MethodName doGetAuthenticationInfo
* @Description 认证配置类
* @Param [authenticationToken]
* @Return AuthenticationInfo
* @Author hisoft
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
if (StringUtils.isEmpty(authenticationToken.getPrincipal())) {
return null;
}
//获取用户信息
String name = authenticationToken.getPrincipal().toString();
User user = loginService.getUserByName(name);
if (user == null) {
//这里返回后会报出对应异常
return null;
} else {
//这里验证authenticationToken和simpleAuthenticationInfo的信息
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, user.getPassword(), getName());
return simpleAuthenticationInfo;
}
}
}
创建三个实体类,分别是用户、角色、权限
@Data
@AllArgsConstructor
public class User {
private String id;
private String userName;
private String password;
/**
* 用户对应的角色集合
*/
private Set<Role> roles;
}
@Data
@AllArgsConstructor
public class Role {
private String id;
private String roleName;
/**
* 角色对应权限集合
*/
private Set<Permissions> permissions;
}
@Data
@AllArgsConstructor
public class Permissions {
private String id;
private String permissionsName;
}
service层
@Service
public class LoginServiceImpl implements LoginService {
@Override
public User getUserByName(String getMapByName) {
return getMapByName(getMapByName);
}
/**
* 模拟数据库查询
*
* @param userName 用户名
* @return User
*/
private User getMapByName(String userName) {
Permissions permissions1 = new Permissions("1", "query");
Permissions permissions2 = new Permissions("2", "add");
Set<Permissions> permissionsSet = new HashSet<>();
permissionsSet.add(permissions1);
permissionsSet.add(permissions2);
Role role = new Role("1", "admin", permissionsSet);
Set<Role> roleSet = new HashSet<>();
roleSet.add(role);
User user = new User("1", "hisoft", "123456", roleSet);
Map<String, User> map = new HashMap<>();
map.put(user.getUserName(), user);
Set<Permissions> permissionsSet1 = new HashSet<>();
permissionsSet1.add(permissions1);
Role role1 = new Role("2", "user", permissionsSet1);
Set<Role> roleSet1 = new HashSet<>();
roleSet1.add(role1);
User user1 = new User("2", "zhangsan", "123456", roleSet1);
map.put(user1.getUserName(), user1);
return map.get(userName);
}
}
controller层
@RestController
@Slf4j
public class LoginController {
@GetMapping("/login")
public String login(User user) {
if (StringUtils.isEmpty(user.getUserName()) || StringUtils.isEmpty(user.getPassword())) {
return "请输入用户名和密码!";
}
//用户认证信息
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
user.getUserName(),
user.getPassword()
);
try {
//进行验证,这里可以捕获异常,然后返回对应信息
subject.login(usernamePasswordToken);
// subject.checkRole("admin");
// subject.checkPermissions("query", "add");
} catch (UnknownAccountException e) {
log.error("用户名不存在!", e);
return "用户名不存在!";
} catch (AuthenticationException e) {
log.error("账号或密码错误!", e);
return "账号或密码错误!";
} catch (AuthorizationException e) {
log.error("没有权限!", e);
return "没有权限";
}
return "login success";
}
@RequiresRoles("admin")
@GetMapping("/admin")
public String admin() {
return "admin success!";
}
@RequiresPermissions("query")
@GetMapping("/index")
public String index() {
return "index success!";
}
@RequiresPermissions("add")
@GetMapping("/add")
public String add() {
return "add success!";
}
}