学习目标:
例如:
学习内容:
例如:
- 了解shiro框架
- 使用jwt生成身份令牌
项目目录:
直接上代码:
这个过滤器是我们的重点,这里我们继承的是Shiro内置的BasicHttpAuthenticationFilter ,一个可以内置了可以自动登录方法的的过滤器,有些同学继承AuthenticatingFilter也是可以的,根据个人情况。
@Component
@Slf4j
public class JWTFilter extends BasicHttpAuthenticationFilter {
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
Subject subject = SecurityUtils.getSubject();
return null != subject && subject.isAuthenticated();
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response){
HttpServletRequest httpServletRequest= (HttpServletRequest) request;
String token = httpServletRequest.getHeader("Authorization");
if(null==token||"".equals(token)){
responseTokenError(response, RCode.INVALID_TOKEN);
return false;
}
JWTToken jwtToken = new JWTToken(token);
try {
SecurityUtils.getSubject().login(jwtToken);
} catch (AuthenticationException e) {
log.error(e.getMessage());
responseTokenError(response,RCode.AUTH_ERROR);
return false;
}
return true;
}
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
httpServletResponse.setStatus(HttpStatus.OK.value());
return false;
}
return super.preHandle(request, response);
}
private void responseTokenError(ServletResponse response,RCode rCode) {
HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
httpServletResponse.setStatus(HttpStatus.OK.value());
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json; charset=utf-8");
try (PrintWriter out = httpServletResponse.getWriter()) {
String data = new Gson().toJson(R.error(rCode));
out.append(data);
} catch (IOException e) {
e.printStackTrace();
log.error(e.getMessage());
}
}
}
整合shiro的配置,shiro可以帮我们做很多事情,比如创建realm、配置shiro过滤器工厂等。
@Configuration
public class ShiroConfig {
@Bean("securityManager")
public DefaultWebSecurityManager getManager(UserRealm realm) {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(realm);
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
manager.setSubjectDAO(subjectDAO);
return manager;
}
@Bean("shiroFilter")
public ShiroFilterFactoryBean factory(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
Map<String, Filter> filterMap = new HashMap<>();
filterMap.put("jwt", new JWTFilter());
factoryBean.setFilters(filterMap);
factoryBean.setSecurityManager(securityManager);
Map<String, String> filterRuleMap = new HashMap<>();
filterRuleMap.put("/**", "jwt");
filterRuleMap.put("/admin/sys/user/login", "anon");
filterRuleMap.put("/user/imgCode", "anon");
filterRuleMap.put("/swagger-ui.html", "anon");
filterRuleMap.put("/webjars/**","anon");
filterRuleMap.put("/swagger-resources/**","anon");
filterRuleMap.put("/v2/**","anon");
filterRuleMap.put("/druid/**","anon");
factoryBean.setFilterChainDefinitionMap(filterRuleMap);
return factoryBean;
}
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}
自定义realm,因为我没有连接数据库这里的用户名和密码都是写死的,根据业务需要可以连接数据库获取
@Service
public class UserRealm extends AuthorizingRealm {
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JWTToken;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
String token = (String) auth.getCredentials();
if(JWTUtils.isExpire(token)){
throw new AuthenticationException("token过期,请重新登入!");
}
String username = JWTUtils.getUsername(token);
if (username == null) {
throw new AuthenticationException("token错误,请重新登入!");
}
String name="admin";
String pwd="3bcbb857c763d1429a24959cb8de2593";
if (! JWTUtils.verify(token, name, pwd)) {
throw new AuthenticationException("密码错误!");
}
return new SimpleAuthenticationInfo("activeUser", token, getName());
}
}
public class JWTToken implements AuthenticationToken {
private String token;
public JWTToken(String token) {
this.token = token;
}
@Override
public Object getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return token;
}
}
jwt生成token
public class JWTUtils {
private static final long EXPIRE_TIME = 6 * 60 * 60 * 1000;
public static boolean verify(String token, String username, String secret) {
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm)
.withClaim("username", username)
.build();
DecodedJWT jwt = 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);
return JWT.create()
.withClaim("username", username)
.withExpiresAt(date)
.sign(algorithm);
} catch (Exception e) {
return null;
}
}
public static boolean isExpire(String token) {
DecodedJWT jwt = JWT.decode(token);
return System.currentTimeMillis() > jwt.getExpiresAt().getTime();
}
}
使用md5进行加密
public static String md5Encryption(String source,String salt){
String algorithmName = "MD5";
int hashIterations = 1024;
SimpleHash simpleHash = new SimpleHash(algorithmName,source,salt,hashIterations);
return simpleHash+"";
}
重载类:用于结果返回
@Data
@AllArgsConstructor
@NoArgsConstructor
public class R {
private Integer code ;
private String msg;
private Object data;
public static R ok(Object data){
return new R(RCode.SUCCESS.getCode(),
RCode.SUCCESS.getMsg(),data);
}
public static R ok(){
return R.ok(null);
}
public static R error(RCode rCode){
return new R(rCode.getCode(),rCode.getMsg(),null);
}
public static R error(String message){
return new R(RCode.SYS_ERROR.getCode(),message,null);
}
public static R error(){
return R.error(RCode.ERROR);
}
}
枚举类
@AllArgsConstructor
@Getter
public enum RCode {
SUCCESS(0,"操作成功"),
ERROR(1,"操作失败"),
SYS_ERROR(500,"系统错误,请与管理员联系"),
SYS_VALID_ERROR(501,"参数校验错误"),
USER_EXIST(1001,"用户已存在"),
USER_NOT_EXIST(1002,"用户不存在!"),
VALID_ERROR(40001,"验证错误!"),
INVALID_TOKEN(40410,"Token无效,您无权访问该接口"),
AUTH_ERROR(40411,"用户名或密码不存在!"),
NO_AUTH_ERROR(40412,"无权访问!"),
QUERY_EXIST(1002,"此数据以重复")
;
private Integer code;
private String msg;
}
到这里就可以开始写controller了,写了一个登陆的接口和两个测试的接口
@RestController
@CrossOrigin
@RequestMapping("/admin/sys/user")
public class TestController {
@GetMapping("/login")
public R login(String username,String password){
String MD5Password = MD5Utils.md5Encryption(username, password);
System.out.println(MD5Password);
String token= JWTUtils.sign(username, MD5Password);
System.out.println(token);
JWTToken jwtToken = new JWTToken(token);
try {
SecurityUtils.getSubject().login(jwtToken);
} catch (AuthenticationException e) {
return R.error("token 错误!");
}
Map<String,Object> maps = new HashMap<>();
maps.put("token",token);
return R.ok(maps);
}
@GetMapping("all")
public String all(){
return "all";
}
@GetMapping("list")
public R list(){
return R.ok("list");
}
}
测试结果:
如果我不携带token是不能访问任何接口的,除登陆接口外
下面访问登陆接口,是可以访问的,并且返回的token令牌,我们可以拿着这个token去访问其他接口
带着token就可以正常访问了,有效期是6个小时
总结: 本人学习这个shiro+jwt也没几天很多原理也还没弄明白,讲的不明白只实现了效果,如过有什么地方说的不对还希望大佬指点一下。
|