master
陈景阳 4 years ago
commit 1ef676f9ae
  1. 3
      .gitignore
  2. 77
      pom.xml
  3. 12
      src/main/java/org/anyin/gitee/shiro/ShiroApplication.java
  4. 24
      src/main/java/org/anyin/gitee/shiro/base/BusinessCodeEnum.java
  5. 21
      src/main/java/org/anyin/gitee/shiro/base/BusinessException.java
  6. 30
      src/main/java/org/anyin/gitee/shiro/base/Response.java
  7. 22
      src/main/java/org/anyin/gitee/shiro/base/SpringContext.java
  8. 64
      src/main/java/org/anyin/gitee/shiro/config/ShiroConfig.java
  9. 16
      src/main/java/org/anyin/gitee/shiro/controller/HomeController.java
  10. 30
      src/main/java/org/anyin/gitee/shiro/controller/LoginController.java
  11. 11
      src/main/java/org/anyin/gitee/shiro/controller/form/LoginForm.java
  12. 15
      src/main/java/org/anyin/gitee/shiro/model/UserInfo.java
  13. 8
      src/main/java/org/anyin/gitee/shiro/service/ILoginService.java
  14. 15
      src/main/java/org/anyin/gitee/shiro/service/ITokenService.java
  15. 8
      src/main/java/org/anyin/gitee/shiro/service/IUserInfoService.java
  16. 41
      src/main/java/org/anyin/gitee/shiro/service/impl/LoginServiceImpl.java
  17. 46
      src/main/java/org/anyin/gitee/shiro/service/impl/TokenServiceImpl.java
  18. 28
      src/main/java/org/anyin/gitee/shiro/service/impl/UserInfoServiceImpl.java
  19. 22
      src/main/java/org/anyin/gitee/shiro/shiro/ShiroToken.java
  20. 12
      src/main/java/org/anyin/gitee/shiro/shiro/TokenCredentialsMatcher.java
  21. 95
      src/main/java/org/anyin/gitee/shiro/shiro/TokenFilter.java
  22. 46
      src/main/java/org/anyin/gitee/shiro/shiro/TokenRealm.java
  23. 3
      src/main/resources/application.yml

3
.gitignore vendored

@ -0,0 +1,3 @@
*.iml
.idea
target

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.6</version>
<relativePath/>
</parent>
<groupId>org.anyin.gitee.shiro</groupId>
<artifactId>shiro-to-token</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<spring.boot.version>2.4.6</spring.boot.version>
<spring.boot.shiro.version>1.8.0</spring.boot.shiro.version>
<hutool.version>5.7.2</hutool.version>
</properties>
<dependencies>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>${spring.boot.shiro.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<finalName>shiro-to-token</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,12 @@
package org.anyin.gitee.shiro;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ShiroApplication {
public static void main(String[] args){
SpringApplication.run(ShiroApplication.class, args);
}
}

@ -0,0 +1,24 @@
package org.anyin.gitee.shiro.base;
import lombok.Getter;
@Getter
public enum BusinessCodeEnum {
USER_UN_LOGIN("1008", "用户未登录"),
USER_NOT_FOUND("B1001", "用户账号密码错误"),
PWD_NOT_MATCH("B1002", "用户账号密码错误"),
TOKEN_INVALID("B1003", "Token失效")
;
private final String code;
private final String msg;
BusinessCodeEnum(String code, String msg) {
this.code = code;
this.msg = msg;
}
public BusinessException getException(){
return new BusinessException(this.code, this.msg);
}
}

@ -0,0 +1,21 @@
package org.anyin.gitee.shiro.base;
import lombok.Data;
@Data
public class BusinessException extends RuntimeException{
private String code;
private String msg;
public BusinessException(BusinessCodeEnum business){
this.code = business.getCode();
this.msg = business.getMsg();
}
public BusinessException(String code, String msg){
this.code = code;
this.msg = code;
}
}

@ -0,0 +1,30 @@
package org.anyin.gitee.shiro.base;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
@Data
public class Response<T> {
private static final String SUCCESS_CODE = "200";
private String code;
private String msg;
private T data;
public Response(){
this.code = SUCCESS_CODE;
}
public Response(T data){
this.data = data;
this.code = SUCCESS_CODE;
}
public Response(String code, String msg){
this.code = code;
this.msg = msg;
}
}

@ -0,0 +1,22 @@
package org.anyin.gitee.shiro.base;
import cn.hutool.extra.spring.SpringUtil;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class SpringContext implements ApplicationContextAware {
private static ApplicationContext context = null;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContext.context = applicationContext;
}
public static <T> T getBean(Class<T> type) {
return context.getBean(type);
}
}

@ -0,0 +1,64 @@
package org.anyin.gitee.shiro.config;
import org.anyin.gitee.shiro.shiro.TokenCredentialsMatcher;
import org.anyin.gitee.shiro.shiro.TokenFilter;
import org.anyin.gitee.shiro.shiro.TokenRealm;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.mgt.SessionsSecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.apache.shiro.web.filter.mgt.DefaultFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Bean
public TokenRealm tokenRealm(){
TokenRealm realm = new TokenRealm();
realm.setCredentialsMatcher(credentialsMatcher());
return realm;
}
@Bean(name = "shiroFilterFactoryBean")
public ShiroFilterFactoryBean shiroFilterFactoryBean(){
// 认证过滤器
Map<String, Filter> filterMap = new HashMap<>();
filterMap.put("token", new TokenFilter());
// 忽略路径
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/login", DefaultFilter.anon.name());
filterChainDefinitionMap.put("/logout", DefaultFilter.anon.name());
filterChainDefinitionMap.put("/**", "token");
// 装配bean
ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
filterFactoryBean.setSecurityManager(securityManager());
filterFactoryBean.setFilters(filterMap);
filterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return filterFactoryBean;
}
@Bean
public CredentialsMatcher credentialsMatcher(){
return new TokenCredentialsMatcher();
}
@Bean
public SessionsSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(tokenRealm());
return securityManager;
}
}

@ -0,0 +1,16 @@
package org.anyin.gitee.shiro.controller;
import org.anyin.gitee.shiro.base.Response;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/home")
public class HomeController {
@GetMapping
public Response<String> home(){
return new Response<>("success");
}
}

@ -0,0 +1,30 @@
package org.anyin.gitee.shiro.controller;
import org.anyin.gitee.shiro.base.Response;
import org.anyin.gitee.shiro.controller.form.LoginForm;
import org.anyin.gitee.shiro.model.UserInfo;
import org.anyin.gitee.shiro.service.ILoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping
public class LoginController {
@Autowired
private ILoginService loginService;
@PostMapping("/login")
public Response<String> login(@RequestBody LoginForm form){
String token = loginService.login(form.getUsername(), form.getPassword());
return new Response<>(token);
}
@PostMapping("/logout")
public Response<String> logout(){
return new Response<>();
}
}

@ -0,0 +1,11 @@
package org.anyin.gitee.shiro.controller.form;
import lombok.Data;
@Data
public class LoginForm {
private String username;
private String password;
}

@ -0,0 +1,15 @@
package org.anyin.gitee.shiro.model;
import lombok.Data;
@Data
public class UserInfo {
private Integer id;
private String username;
private String nickName;
private String password;
}

@ -0,0 +1,8 @@
package org.anyin.gitee.shiro.service;
public interface ILoginService {
String login(String username, String password);
void logout(String token);
}

@ -0,0 +1,15 @@
package org.anyin.gitee.shiro.service;
import org.anyin.gitee.shiro.model.UserInfo;
import org.anyin.gitee.shiro.shiro.ShiroToken;
public interface ITokenService {
boolean check(String token);
ShiroToken createToken(String token, UserInfo userInfo);
ShiroToken createToken(UserInfo userInfo);
UserInfo getUserInfo(String token);
}

@ -0,0 +1,8 @@
package org.anyin.gitee.shiro.service;
import org.anyin.gitee.shiro.model.UserInfo;
public interface IUserInfoService {
UserInfo findByUsername(String username);
}

@ -0,0 +1,41 @@
package org.anyin.gitee.shiro.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.anyin.gitee.shiro.base.BusinessCodeEnum;
import org.anyin.gitee.shiro.model.UserInfo;
import org.anyin.gitee.shiro.service.ILoginService;
import org.anyin.gitee.shiro.service.ITokenService;
import org.anyin.gitee.shiro.service.IUserInfoService;
import org.anyin.gitee.shiro.shiro.ShiroToken;
import org.apache.shiro.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class LoginServiceImpl implements ILoginService {
@Autowired
private IUserInfoService userInfoService;
@Autowired
private ITokenService tokenService;
@Override
public String login(String username, String password) {
UserInfo userInfo = userInfoService.findByUsername(username);
if(!userInfo.getPassword().equals(password)){
throw BusinessCodeEnum.PWD_NOT_MATCH.getException();
}
// shiro 框架登录
ShiroToken token = tokenService.createToken(userInfo);
SecurityUtils.getSubject().login(token);
return (String) token.getCredentials();
}
@Override
public void logout(String token) {
}
}

@ -0,0 +1,46 @@
package org.anyin.gitee.shiro.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.anyin.gitee.shiro.model.UserInfo;
import org.anyin.gitee.shiro.service.ITokenService;
import org.anyin.gitee.shiro.shiro.ShiroToken;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@Service
@Slf4j
public class TokenServiceImpl implements ITokenService {
private Map<String, UserInfo> tokenUserMap = new HashMap<>();
@Override
public boolean check(String token) {
log.info("check token is valid");
return true;
}
@Override
public ShiroToken createToken(String token, UserInfo userInfo) {
String targetToken = token;
if(ObjectUtils.isEmpty(targetToken)){
targetToken = UUID.randomUUID().toString();
}
// token 和 用户信息的映射,可以使用Redis存储 或者 替换为JWT
tokenUserMap.put(targetToken, userInfo);
return new ShiroToken(targetToken);
}
@Override
public ShiroToken createToken(UserInfo userInfo) {
return this.createToken("", userInfo);
}
@Override
public UserInfo getUserInfo(String token) {
return tokenUserMap.get(token);
}
}

@ -0,0 +1,28 @@
package org.anyin.gitee.shiro.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.anyin.gitee.shiro.base.BusinessCodeEnum;
import org.anyin.gitee.shiro.model.UserInfo;
import org.anyin.gitee.shiro.service.IUserInfoService;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class UserInfoServiceImpl implements IUserInfoService {
private static final String DEFAULT_USERNAME = "anyin";
@Override
public UserInfo findByUsername(String username) {
// 从数据库查询用户信息
if(DEFAULT_USERNAME.equals(username)){
UserInfo userInfo = new UserInfo();
userInfo.setId(1);
userInfo.setNickName("暗音");
userInfo.setUsername("anyin");
userInfo.setPassword("123456");
return userInfo;
}
throw BusinessCodeEnum.USER_NOT_FOUND.getException();
}
}

@ -0,0 +1,22 @@
package org.anyin.gitee.shiro.shiro;
import org.apache.shiro.authc.AuthenticationToken;
public class ShiroToken implements AuthenticationToken {
private String token;
public ShiroToken(String token) {
this.token = token;
}
@Override
public Object getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return token;
}
}

@ -0,0 +1,12 @@
package org.anyin.gitee.shiro.shiro;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.credential.CredentialsMatcher;
public class TokenCredentialsMatcher implements CredentialsMatcher {
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
return token instanceof ShiroToken;
}
}

@ -0,0 +1,95 @@
package org.anyin.gitee.shiro.shiro;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.anyin.gitee.shiro.base.BusinessCodeEnum;
import org.anyin.gitee.shiro.base.Response;
import org.anyin.gitee.shiro.base.SpringContext;
import org.anyin.gitee.shiro.service.ITokenService;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
public class TokenFilter extends AuthenticatingFilter {
private static final String X_TOKEN = "X-Token";
private ITokenService tokenService = null;
/**
* 创建Token, 支持自定义Token
*/
@Override
protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
String token = this.getToken((HttpServletRequest)servletRequest);
if(ObjectUtils.isEmpty(token)){
log.error("token is empty");
return null;
}
return new ShiroToken(token);
}
/**
* 兼容跨域
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
return ((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name());
}
@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String token = this.getToken(request);
if(ObjectUtils.isEmpty(token)){
this.respUnLogin(request, response);
return false;
}
// 校验Token的有效性
if(tokenService == null){
tokenService = SpringContext.getBean(ITokenService.class);
}
if(!tokenService.check(token)){
this.respUnLogin(request, response);
}
// 根据token获取用户信息,会执行 TokenRealm#doGetAuthenticationInfo 方法
return executeLogin(servletRequest, servletResponse);
}
private void respUnLogin(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("application/json;charset=utf-8");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
Response resp = new Response(BusinessCodeEnum.USER_UN_LOGIN.getCode(), BusinessCodeEnum.USER_UN_LOGIN.getMsg());
response.getWriter().print(JSONUtil.toJsonStr(resp));
}
/**
* 获取token
* 优先从header获取
* 如果没有则从parameter获取
* @param request request
* @return token
*/
private String getToken(HttpServletRequest request){
String token = request.getHeader(X_TOKEN);
if(ObjectUtils.isEmpty(token)){
token = request.getParameter(X_TOKEN);
}
return token;
}
}

@ -0,0 +1,46 @@
package org.anyin.gitee.shiro.shiro;
import lombok.extern.slf4j.Slf4j;
import org.anyin.gitee.shiro.base.BusinessCodeEnum;
import org.anyin.gitee.shiro.model.UserInfo;
import org.anyin.gitee.shiro.service.ITokenService;
import org.anyin.gitee.shiro.service.IUserInfoService;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
@Slf4j
public class TokenRealm extends AuthorizingRealm {
@Autowired
@Lazy
private ITokenService tokenService;
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof ShiroToken;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
log.info("user do authorization: {}", principalCollection);
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
log.info("user do authentication: {}", authenticationToken);
ShiroToken token = (ShiroToken)authenticationToken;
UserInfo userInfo = tokenService.getUserInfo(token.getCredentials().toString());
if(userInfo == null){
throw BusinessCodeEnum.TOKEN_INVALID.getException();
}
return new SimpleAuthenticationInfo(userInfo.getUsername(), userInfo.getPassword(), userInfo.getNickName());
}
}

@ -0,0 +1,3 @@
spring:
application:
name: shiro-to-token
Loading…
Cancel
Save