Springboot采用的是2.2.0 下面是jwt 和shiro的依赖
<dependency>
<groupId>com.auth0groupId>
<artifactId>java-jwtartifactId>
<version>3.2.0version>
dependency>
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-springartifactId>
<version>1.4.0version>
dependency>
原先的项目有Realm我还是集成了自己的Realm来做用户名密码的校验然后我又写了一个JWTRealm也就是双Realm来做
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.io.UnsupportedEncodingException;
import java.util.Date;
/**
* @Author: naine
* @Description:
* @Date: 10:46 上午 2020/9/8
*/
public class JWTUtil {
// 过期时间 24 小时
private static final long EXPIRE_TIME = 60 * 24 * 60 * 1000;
// 密钥
private static final String SECRET = "Naine";
public static String createToken(String username) {
try {
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
Algorithm algorithm = Algorithm.HMAC256(SECRET);
// 附带username信息
return JWT.create()
.withClaim("username", username)
//到期时间
.withExpiresAt(date)
//创建一个新的JWT,并使用给定的算法进行标记
.sign(algorithm);
} catch (Exception e) {
return null;
}
}
public static boolean verify(String token, String username) {
try {
Algorithm algorithm = Algorithm.HMAC256(SECRET);
//在token中附带了username信息
JWTVerifier verifier = JWT.require(algorithm)
.withClaim("username", username)
.build();
//验证 token
verifier.verify(token);
return true;
} catch (Exception exception) {
return false;
}
}
public static String getUsername(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim("username").asString();
} catch (JWTDecodeException e) {
return null;
}
}
public static String sign(String username, String secret) {
try {
Date date = new Date(System.currentTimeMillis()+EXPIRE_TIME);
Algorithm algorithm = Algorithm.HMAC256(secret);
// 附带username信息
return JWT.create()
.withClaim("username", username)
.withExpiresAt(date)
.sign(algorithm);
} catch (Exception e) {
return null;
}
}
}
生成属于JWT的token放弃shiro自带的UsernamePasswordToken
public class JWTToken implements AuthenticationToken {
private String token;
public JWTToken(String token) {
this.token = token;
}
//获取用户名
@Override
public Object getPrincipal() {
return JWTUtil.getUsername(token);
}
//获取密码
@Override
public Object getCredentials() {
return token;
}
}
Shiro的底层通过JWTCredentialsMatcher方法来验证用户名密码对不对,但是现在我们用的是token这里得改成token是不是正确的所以改掉这个方法
public class JwtRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Autowired
private RoleService roleService;
@Autowired
private PermissionService permissionService;
public JwtRealm(){
// JWT方法才是真正验证token的地方
this.setCredentialsMatcher(new JWTCredentialsMatcher());
}
//判断AuthenticationToken 参数是不是 JWTToken类型如果是才进入
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JWTToken ;
}
//授权方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String username = JWTUtil.getUsername(principalCollection.toString());
Role roles = roleService.selectRolebyusername(username);
List<Permission> permissions = permissionService.selectPermissionbyusename(username);
SimpleAuthorizationInfo s = new SimpleAuthorizationInfo();
if (roles != null) {
s.addRole(roles.getName());
} else {
return null;
}
Set<String> permissions1 = new HashSet<>();
if (!permissions.isEmpty() && permissions.get(0) != null) {
for (Permission permission : permissions) {
permissions1.add(permission.getPerms().trim());
}
s.setStringPermissions(permissions1);
} else {
return null;
}
return s;
}
//登录方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String token = (String) authenticationToken.getCredentials();
return new SimpleAuthenticationInfo(token, token, this.getName());
}
}
在这个方法里true就是登陆成功 false就是登陆失败, 用JWTUtil里面来验证token是不是正确的,这里可能有和redis做比较的,但是思路都是差不多的就是比对你的token是不是正确的,如果正确就是登陆成功,错误就是登陆失败
@Slf4j
public class JWTCredentialsMatcher implements CredentialsMatcher {
@Override
public boolean doCredentialsMatch(AuthenticationToken authenticationToken, AuthenticationInfo authenticationInfo) {
String token = (String) authenticationToken.getCredentials();
// 解密获得username,用于和数据库进行对比
String username = JWTUtil.getUsername(token);
try {
JWTUtil.verify(token,username);
return true;
} catch (Exception e) {
log.error("Token Error:{}", e.getMessage());
}
return false;
}
在这个过滤器中用来替代shiro的验证权限的自带的过滤器
@Slf4j
public class JwtFilter extends BasicHttpAuthenticationFilter {
@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
return false;
}
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String token = httpServletRequest.getHeader("token");
JWTToken jwtToken = new JWTToken(token);
// 提交给realm进行登入,如果错误它会抛出异常并被捕获
getSubject(request, response).login(jwtToken);
// 如果没有抛出异常则代表登入成功,返回true
return true;
}
//首先会进入这个方法
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
if (isLoginAttempt(request, response)) {
//如果存在,则进入 executeLogin 方法执行登入,检查 token 是否正确
try {
executeLogin(request, response);
return true;
} catch (Exception e) {
//token 错误
throw new Exception(300, "请登录");
}
}else {
throw new MyException(300, "请登录");
}
//如果请求头不存在 token,则可能是执行登陆操作或者是游客状态访问,无需检查 token,直接返回 true
}
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
return super.onLoginFailure(token, e, request, response);
}
// 查看请求头是否带token
@Override
protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
HttpServletRequest req = (HttpServletRequest) request;
String token = req.getHeader("token");
return token != null;
}
}
public class StatelessDefaultSubjectFactory extends DefaultWebSubjectFactory {
@Override
public Subject createSubject(SubjectContext context) {
context.setSessionCreationEnabled(false);
return super.createSubject(context);
}
}
将上一步的过滤器加入shiro过滤器,过滤器不需要交给Spring管理,不然过滤器就变成全部过滤器了。将authc 改为 jwt ,使用我们自己写的过滤器,RetryLimitHashedCredentialsMatcher() 这个是另一个验证账户名密码的Realm里面的和JWTRealm 无关 当时是因为要集成验证码登录的功能
@Configuration
public class ShiroConfigBean {
//配置过滤器
@Bean
public ShiroFilterFactoryBean shiroFilter(){
ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
//添加过滤器
Map<String,Filter> filterMap =shiroFilterFactoryBean.getFilters();
filterMap.put("jwt", new JwtFilter());
shiroFilterFactoryBean.setFilters(filterMap);
shiroFilterFactoryBean.setSecurityManager(securityManager(new
RetryLimitHashedCredentialsMatcher()));
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/jwt/login","anon");
filterChainDefinitionMap.put("/**", "jwt");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
//配置 securityManager
@Bean
public DefaultWebSecurityManager securityManager(@Qualifier("RetryLimitHashedCredentialsMatcher") RetryLimitHashedCredentialsMatcher matcher){
DefaultWebSecurityManager defaultWebSecurityManager=new DefaultWebSecurityManager();
//关闭shiro的seesionid
DefaultSubjectDAO defaultSubjectDAO = new DefaultSubjectDAO();
DefaultWebSessionStorageEvaluator sessionStorageEvaluator = new DefaultWebSessionStorageEvaluator();
sessionStorageEvaluator.setSessionStorageEnabled(false);
defaultSubjectDAO.setSessionStorageEvaluator(sessionStorageEvaluator);
defaultWebSecurityManager.setSubjectFactory(new StatelessDefaultSubjectFactory());
//开始shiro验证策略当前策略一个Realm 通过代表全部通过
ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
authenticator.setAuthenticationStrategy(new FirstSuccessfulStrategy());
defaultWebSecurityManager.setAuthenticator(authenticator);
//配置多个Realm
defaultWebSecurityManager.setRealms(Arrays.asList(jwtShiroRealm(),myshiroRealm(matcher)));
return defaultWebSecurityManager;
}
//JwtRealm
@Bean(name = "jwtRealm")
public JwtRealm jwtShiroRealm() {
JwtRealm myShiroRealm = new JwtRealm();
return myShiroRealm;
}
//开启shiro注解
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
这里也可以写自己的用户名密码的比对总之就是验证成功创建token
@PostMapping("/login")
public Map<String, Object> login(User user, HttpSession httpSession, String vcode, boolean remember, ServletRequest request, HttpServletResponse response, HttpServletRequest hrequest, String cid) {
Subject subject = SecurityUtils.getSubject();
Map<String, Object> objectMap = new HashMap<>();
EasyTypeToken token = new EasyTypeToken(user.getUsername(), user.getPassword(), remember);
try {
subject.login(token);
objectMap.put("code", "0");
objectMap.put("msg", "登录成功");
objectMap.put("token", JWTUtil.createToken(user.getUsername()));
log.info(user.getUsername() + "密码登录成功");
} catch (IncorrectCredentialsException e) {
objectMap.put("code", "1");
objectMap.put("msg", "用户名密码错误");
log.info(user.getUsername() + "登录密码错误");
} catch (LockedAccountException e) {
objectMap.put("code", "2");
objectMap.put("msg", "登录失败,该用户已被冻结");
log.info(user.getUsername() + "登录失败,该用户已被冻结");
} catch (UnknownAccountException e) {
objectMap.put("code", "2");
objectMap.put("msg", "请注册");
log.info(user.getUsername() + "没有该用户");
} catch (Exception e) {
e.printStackTrace();
log.error(e.getMessage());
throw new MyException(LoginEnum.UNKNOWN_ERROR.getCode(), LoginEnum.UNKNOWN_ERROR.getMsg());
}
return objectMap;
}
登录成功截图