JAVA SSM项目 JWT搭建及服务端鉴权(自定义注解实现)


本人菜鸟一枚,不太喜欢写东西,最近搭建了个JWT鉴权的Demo,好东西我就不藏着了,分享给大家。当然注释我会尽量写的详细些。

表数据结构

简单的用户角色表

Maven依赖

		<dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.6.0</version>
        </dependency>

自己封装的ReturnResult和ReturnMsg

下面是返回结果,这里比较简单,我就不写注释了

public class ReturnResult {
	private Object data;
	private String code;
	private String msg;

	private ReturnResult( Object data, String code, String msg) {

		this.data = data;
		this.code = code;
		this.msg = msg;
	}

	private ReturnResult(Object data) {
		this.data = data;
		this.code = ReturnMsg.SUCCESS.getCode();
		this.msg = ReturnMsg.SUCCESS.getMsg();
	}

	private ReturnResult(String code, String msg) {
		this.code = code;
		this.msg = msg;
	}

	private ReturnResult() {
		this.code = ReturnMsg.ERROR.getCode();
		this.msg = ReturnMsg.ERROR.getMsg();
	}
	
	public static ReturnResult success( Object data, String code, String msg) {
		return new ReturnResult( data, code, msg);
	}


	public static ReturnResult success(Object data) {
		return new ReturnResult(data);
	}


	public static ReturnResult success() {
		return new ReturnResult("");
	}


	public static ReturnResult error() {
		return new ReturnResult();
	}

	public static ReturnResult error(String code, String msg) {
		return new ReturnResult(code, msg);
	}
	//getter和setter

}

下面是返回信息

public enum ReturnMsg {
	SUCCESS("0", "操作成功"),
	EXCEPTION1("10001", "未知异常"),
	EXCEPTION2("10002", "操作失败"),
	EXCEPTION3("10003", "上传失败"),
	EXCEPTION4("10004", "账号或密码错误"),
	EXCEPTION5("10005", "自定义异常"),
	ERROR("-1", "失败");
	
	private String code;
	private String msg;
	
	private ReturnMsg (String code, String msg) {
		this.code = code;
		this.msg = msg;
	}
	
	public String getCode() {
		return code;
	}
	public void setCode(String code) {
		this.code = code;
	}
	public String getMsg() {
		return msg;
	}
	public void setMsg(String msg) {
		this.msg = msg;
	}
}

首先是登录的Controller

@RestController
@RequestMapping("user")
public class LoginController {
	
	@Autowired
	private JwtLoginService jwtLoginService;

	@Autowired
	//这里是查用户的详细信息,代码我就不贴了
	private UserService userService;

	@RequestMapping("login")
	public ReturnResult login(@RequestBody Usr usr){

		//这里会把生成的jwt返回
		String jwt = jwtLoginService.login(usr);
		//如果jwt为空,说明用户未登录,直接返回个错误
		if (jwt==null||"".equals(jwt)){
			return ReturnResult.error();
		}
		
		Usr usrvo = userService.menuByUserName(usr.getUsername());
		//这里会把用户详情和jwt带给前端
		return ReturnResult.success(usrvo,ReturnMsg.SUCCESS.getCode(),jwt) ;
	}
}

jwt接口及实现类

public interface JwtLoginService {
     String login(Usr userLogin);
}
@Service
public class JwtLoginServiceImpl implements JwtLoginService {

    @Autowired
    private UsrMapper usrMapper;

    public String login(Usr userLogin){
    	//这里很简单的进行校验,如果能在数据库里查到传入的用户名和密码,说明登录成功,这里可以改成MD5加密校验
        Usr usr = usrMapper.LoginByNamePass(userLogin.getUsername(), userLogin.getPassword());
        //没查到说明登录失败
        if(usr == null){
            return null;
        }else {
            //登录成功 设置jwt
            JWTUtils util = new JWTUtils();
            //将查询到的角色ID存在一个数组里面写到jwt中,后面鉴权会用到
            Map<String, Object> payload = new HashMap<String, Object>();
            //这里我在Usr中封装了个List<Role>  可以使用Mybatis封装进去,下面我贴出UsrMapper的部分代码
            List<Role> roleList = usr.getRoleList();
            List<Long> roles = new ArrayList<>();
            if (roleList!=null&&roleList.size()>0){
                for (Role role : roleList) {
                    roles.add(role.getId());
                }
            }
            payload.put("roles", roles);

            try {
            	//86400000是过期时间  24小时
                String jwt =util.createJWT("jwt", usr.getUsername(), 86400000,payload);
                return jwt;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    }
}

UsrMapper 这里大家自己来实现,也可以用SpringDataJPA

//登录根据用户名密码查找
    @Select("SELECT * FROM usr WHERE username= #{username} AND PASSWORD = #{password} ")
    @Results(value = {
            @Result(id=true,property = "id",column = "id"),
            @Result(property = "roleList",column = "id",many = @Many(select = "com.xxx.mapper.RoleMapper.findByUid"))
    })
    Usr LoginByNamePass(@Param("username") String username,@Param("password") String password);

接下来是工具类

public class JWTUtils {
    public String createJWT(String id, String subject, long ttlMillis,Map<String, Object> claims) throws Exception {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; // 指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。
        long nowMillis = System.currentTimeMillis();// 生成JWT的时间
        Date now = new Date(nowMillis);
        SecretKey key = generalKey();
        // 下面就是在为payload添加各种标准声明和私有声明了
        JwtBuilder builder = Jwts.builder() // 这里其实就是new一个JwtBuilder,设置jwt的body
                .setClaims(claims) // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
                .setId(id) // 这里是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
                .setIssuedAt(now) //  jwt的签发时间
                .setSubject(subject) // 这里代表这个JWT的主体,可以是用户名,id,保证唯一就可以了
                .signWith(signatureAlgorithm, key);// 设置签名使用的签名算法和签名使用的秘钥
        if (ttlMillis >= 0) {
            long expMillis = nowMillis + ttlMillis;
            Date exp = new Date(expMillis);
            builder.setExpiration(exp); // 设置过期时间
        }
        return builder.compact(); // 生成jwt
    }

    /**
     * 解密jwt
     */
    public Claims parseJWT(String jwt) throws Exception {
        SecretKey key = generalKey(); // 签名秘钥,和生成的签名的秘钥一模一样
        Claims claims = Jwts.parser() // 得到DefaultJwtParser
                .setSigningKey(key) // 设置签名的秘钥
                .parseClaimsJws(jwt).getBody();// 设置需要解析的jwt
        return claims;
    }

    /**
     * 由字符串生成加密key
     */
    public SecretKey generalKey() {
        PropertiesUtils propertiesUtils = new PropertiesUtils();
        byte[] encodedKey = Base64.decodeBase64(123);// 本地的密码解码,这里自定义
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");// 根据给定的字节数组使用AES加密算法构造一个密钥,使用
        return key;
    }

}

接下来是关键,拦截器

@Component
public class JWTInterceptor implements HandlerInterceptor {

    public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
            throws Exception {
    }

    public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)
            throws Exception {
    }

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        JWTUtils util = new JWTUtils();
        //前端请求必须在请求头中带Authorization
        String jwt = request.getHeader("Authorization");
        //获得realIP,用于白名单或黑名单,这是我配了Nginx,把真实的IP写到请求头里面了,这里可以不管
        String realIP = request.getHeader("X-Real-IP");
        if (realIP != null) {
            if (realIP.equals("xxx.xxx.xxx.xxx"))) {
                return true;
            }
        }
        if (jwt == null) {
            //这里应该用自定义异常
            response.getWriter().write("未登录,请重新登录后操作");
            return false;
        } else {
            Claims c = util.parseJWT(jwt);
            //拿到自定义的角色数组
            List<Integer> list = (List<Integer>) c.get("roles");
            if (list == null || list.size() < 1) {
                throw new InsufficientAuthorityException("非法用户");

            }
            
            if (handler instanceof HandlerMethod) {
                HandlerMethod handlerMethod = (HandlerMethod) handler;
                //拿到方法上的注解,方法上的注解优先
                XXSecurity eMethod = handlerMethod.getMethodAnnotation(XXSecurity .class);
                //拿不到,也能写在了类上,下面做判断
                if (eMethod != null) {
                    int[] value = eMethod.value();
                    //注解内容为空,说明这个方法不需要做权限控制
                    if (value.length == 0) {
                        return true;
                    }
                    //注解中的权限包含该用户的权限就直接放心
                    for (int i : value) {
                        if (list.contains(i)) {
                            return true;
                        }
                    }
                    //走完上面的循环,说明用户没有权限,我这里使用的自定义异常,当然return false也可以
                    throw new InsufficientAuthorityException("权限不足");

                } else {
                //拿到类上的注解,和上面同理
                    XXSecurity eType = handlerMethod.getMethod().getDeclaringClass().getAnnotation(XXSecurity .class);
                    if (eType != null) {
                        int[] value = eType.value();
                        if (value.length == 0) {
                            return true;
                        }
                        for (int i : value) {
                            if (list.contains(i)) {
                                return true;
                            }
                        }
                        //直接return false也行
                        throw new InsufficientAuthorityException("权限不足");
                    } else {
                        return true;
                    }
                }
            }
            //走到这里说明在方法和类上都没有权限注解,直接放行
            return true;
        }
    }
}

记得在springmvc.xml中配置

  <mvc:interceptors>
        <!-- 使用bean定义一个Interceptor,直接定义在mvc:interceptors根下面的Interceptor将拦截所有的请求 -->
        <mvc:interceptor>
            <!-- 进行拦截:/**表示拦截所有controller -->
            <mvc:mapping path="/**" />
            <!-- 不进行拦截 -->
            <mvc:exclude-mapping path="/user/login.do" />
            <!--根据自己的-->
            <bean class="com.xxx.jwt.interceptor.JWTInterceptor" />
        </mvc:interceptor>
    </mvc:interceptors>

权限注解,直接拿去用

/**
 * 权限验证注解,可以加在类或方法上
 * 方法上的优先
 * 默认值是该方法所有人都可以访问
 */
 //类和方法上生效
@Target({ElementType.METHOD,ElementType.TYPE})
//运行时有效
@Retention(RetentionPolicy.RUNTIME)
//在javadoc文档中使用(这个不重要)
@Documented
public @interface XXSecurity {
    int [] value() default {};
}

定义个常量类

public class RoleConstant {
    public static final int STU=1;
    public static final int TEA=2;
    public static final int EDU=3;
    public static final int TEA_M=4;
}

我把表也贴上来
在这里插入图片描述

最后,用起来就相当简单了

类上方法上任意加,方法上优先

@RestController
@RequestMapping("user")
@XXSecurity(RoleConstant.STU)
public class UserController {

    @Reference
    private UserService userService;

    @RequestMapping("menu")
    @XXSecurity({RoleConstant.TEA,RoleConstant.STU})
    public ReturnResult menu(String username) {
        Usr usr = userService.menuByUserName(username);
        return ReturnResult.success(usr);
    }
 }

第一次写博客,写的可能不太具体,有什么疑问可以加我qq 540814390,欢迎来撩,我也会不定期的更新,大家关注一下。

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值