博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
spring-boot-note8----shiro-jwt整合
阅读量:4074 次
发布时间:2019-05-25

本文共 13275 字,大约阅读时间需要 44 分钟。

   spring-boot 整合shiro和jwt的认证方式

前言:shiro框架是如今web开发做权限控制非常流行的,JWT就是(JSON Web Token),即如何存储认证后的信息的一种方式,比如以前登陆后,判断username,password如果ok,就存储根据username找出的userinfo到session中。用户是否登陆,判断session中是否有userinfo。而JWT就是把认证后的信息采用一种格式token,明文存储在客户端,客服端每次请求带上这个token,可以判断这个token是否是我服务端颁发的。

一、jwt的基本演示

/** * JWT简易工具类 */public class JWTUtil {    private static final Logger log = LoggerFactory.getLogger(JWTUtil.class);    /**     * 过期时间5分钟     */    private static final long EXPIRE_TIME = 5 * 60 * 1000;    /**     * 生成签名,5min后过期     * @param username 用户名     * @param secret   用户的密码     * @return 加密的token     */    public static String sign(String username, String secret) {        Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);        Algorithm algorithm = Algorithm.HMAC256(secret);        return JWT.create().withClaim("username", username).withExpiresAt(date).sign(algorithm);    }    /**     * 校验token是否正确     * @param token  密钥     * @param secret 用户的密码     * @return 是否正确     */    public static boolean verify(String token, String username, String secret) {        Algorithm algorithm = Algorithm.HMAC256(secret);        JWTVerifier verifier = JWT.require(algorithm).withClaim("username", username).build();        try {			DecodedJWT jwt = verifier.verify(token);		} catch (JWTVerificationException e) {			return false;		}        return true;    }    /**     * 获得token中的信息:无需secret解密也能获得     */    public static String getUsername(String token) {    	return getClaim(token,"username");    }        public static String getClaim(String token,String key) {    	  DecodedJWT jwt = JWT.decode(token);          return jwt.getClaim(key).asString();    }     	public static void main(String[] args) {		String username = "myUsername";		String secret = "123456";		String token = sign(username, secret); // eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MDk2NjYxNjMsInVzZXJuYW1lIjoibXlVc2VybmFtZSJ9._uOb80rq2XkhtzLDR3mqF-2TZt_Y1zcVVg79loLZOzM		System.out.println(token);		boolean verify = verify(token, username, secret);		System.out.println(verify); // true		String usernames = getUsername(token);		System.out.println(usernames); // myUsername	}}

 上面主要有三个方法,sign()方法生成token,verify(token,username,secret)方法用于校验token是否有效 , getClaim()及其类似的方法是用来获取存储的信息。

二、web项目演示

1、项目结构

2、pom.xml

com.auth0
java-jwt
3.4.0
org.apache.shiro
shiro-spring
1.4.0
slf4j-api
org.slf4j
org.apache.shiro
shiro-ehcache
1.4.0
slf4j-api
org.slf4j

3、pojo、service等辅助类

public class User {    private Integer id;    private String username;    private String name;    private String password;    private String salt;    private String phone;    private String tips;    private Integer state;    private Date createdTime;    private Date updatedTime;
public class Role {    private Integer id;    private String role;    private String description;    private Date createdTime;    private Date updatedTime;
public class UserInfo extends User{		private List
roles = new ArrayList<>(); public List
getRoles() { return roles; } public void setRoles(List
roles) { this.roles = roles; } }
@Servicepublic class UserService {	static List
users = new ArrayList<>(); { users.add("admin"); users.add("guest"); users.add("admin99"); } /** * 根据用户名查询用户信息,没有找到返回null,找到返回全信息 */ public UserInfo findByUsername(String username) { if (users.contains(username)) { UserInfo userinfo = new UserInfo(); userinfo.setUsername(username); userinfo.setState(1); userinfo.setCreatedTime(new Date()); userinfo.setName("系统管理员"); userinfo.setSalt("ef748186673033723bbf4e056f4ec92e"); userinfo.setPassword("331be47d5bc559cc9a32a86a04e5ef32"); List
roles = new ArrayList<>(); Role role = new Role(); role.setId(1); role.setRole("admin"); role.setDescription("超级管理员"); roles.add(role); userinfo.setRoles(roles); return userinfo; } return null; }}

4、 主要流程配置等文字描述:

1) 流程:创建一个login登陆接口,用做用户登陆时创建token,并且手动保存这个token,另外创建一个/api/v1/hello接口,使用自己手动保存的token进行认证。在这个流程中,

登陆就是生成token的过程,并且返回到前端保存,那么每次需要jwt校验权限的路径,我们需要配置一个拦截器

2) 拦截器:这个拦截器基于shiro的BasicHttpAuthenticationFilter拦截器,主要功能就是取出header头Authorization的值,执行 getSubject(request, response).login(token);操作,

而login操作我们知道,最终调用的realm中的doGetAuthenticationInfo方法

3)自定义的realm:对token中提取username进行查询等操作,并且校验token的合法性,如果没有问题,就返回SimpleAuthenticationInfo(token,tokenStr., this)。

5、核心配置类

1) JWTToken

/** * jwttoken只有一个字段token,并且getPrincipal和getCredentials都是得到这一个字段的值。 */public class JWTToken implements AuthenticationToken {	private static final long serialVersionUID = -329196857201301194L;	    /**     * jwt加密token     */    private String token;    public JWTToken(String token) {        this.token = token;    }    @Override    public Object getPrincipal() {        return token;    }    @Override    public Object getCredentials() {        return token;    }}

2、 JWTFilter

public class JWTFilter extends BasicHttpAuthenticationFilter {		 private Logger LOGGER = LoggerFactory.getLogger(this.getClass());		 /**     * 判断用户是否想要登入。检测header里面是否包含Authorization字段即可     */    @Override    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {        HttpServletRequest req = (HttpServletRequest) request;        String authorization = req.getHeader("Authorization");        return authorization != null;    }        // 执行登陆,如果登陆失败会抛出异常。    @Override    protected boolean executeLogin(ServletRequest request, ServletResponse response) {        HttpServletRequest httpServletRequest = (HttpServletRequest) request;        String authorization = httpServletRequest.getHeader("Authorization");        JWTToken token = new JWTToken(authorization);        getSubject(request, response).login(token);        return true;    }    /**     *  如果有Authorization字段的都需自动执行登陆操作,否则返回true,表示进入后面流程。     */    @Override    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {        if (isLoginAttempt(request, response)) {            return executeLogin(request, response);        }        return true;    }    /**     * 将非法请求 /401     */    private void response401(ServletRequest req, ServletResponse resp) {        try {            HttpServletResponse httpServletResponse = (HttpServletResponse) resp;            httpServletResponse.sendRedirect("/401");        } catch (IOException e) {            LOGGER.error(e.getMessage());        }    }}

3 、myRealm

public class MyRealm extends AuthorizingRealm {	private static final Logger _logger = LoggerFactory.getLogger(MyRealm.class);		@Override    public boolean supports(AuthenticationToken token) {        return token instanceof AuthenticationToken;    }        @Autowired    UserService userService;	/**	 * 授权	 */	@Override	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {		String username = JWTUtil.getUsername(principals.toString());		UserInfo userinfo = userService.findByUsername(username);		SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();		if(userinfo==null) {return authorizationInfo;}		for (Role role : userinfo.getRoles()) {			authorizationInfo.addRole(role.getRole());		}		return authorizationInfo;	}	/**	 * 认证	 */	@Override	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {		String tokenStr = (String) token.getCredentials();		String username = JWTUtil.getUsername(tokenStr);		if (username == null) {			throw new AuthenticationException("token invalid");		}		UserInfo userinfo = userService.findByUsername(username);		if (userinfo == null) {			throw new AuthenticationException("用户不存在!");		}        if (!JWTUtil.verify(tokenStr, username, userinfo.getPassword())) {            throw new AuthenticationException("Token认证失败");        }        // 这里第二个参数是密码,如果没有配置凭证匹配算法,那么默认就是对getCredentials()和tokenStr直接对比,而二个又是一致的。        return new SimpleAuthenticationInfo(token, tokenStr, "my_realm");	}}

4、shiroConfig

@Configurationpublic class ShiroConfig {	@Bean	public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();		shiroFilterFactoryBean.setSecurityManager(securityManager);		// 验证码过滤器		Map
filtersMap = shiroFilterFactoryBean.getFilters(); filtersMap.put("jwt", new JWTFilter()); shiroFilterFactoryBean.setFilters(filtersMap); Map
filterChainDefinitionMap = new LinkedHashMap
(); // 其他的全部进行jwt拦截器进行处理 filterChainDefinitionMap.put("/**", "jwt"); // 访问401和404页面不通过我们的Filter filterChainDefinitionMap.put("/401", "anon"); filterChainDefinitionMap.put("/404", "anon"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 设置realm. securityManager.setRealm(myShiroRealm()); //注入缓存管理器 securityManager.setCacheManager(ehCacheManager()); /* 关闭shiro自带的session */ DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO(); DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator(); defaultSessionStorageEvaluator.setSessionStorageEnabled(false); subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator); securityManager.setSubjectDAO(subjectDAO); return securityManager; } //自定义Realm @Bean public MyRealm myShiroRealm() { MyRealm myShiroRealm = new MyRealm(); return myShiroRealm; } //加入注解的使用,可使用aop切面 @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } //生命周期 @Bean(name = "lifecycleBeanPostProcessor") public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } @Bean public EhCacheManager ehCacheManager() { EhCacheManager cacheManager = new EhCacheManager(); cacheManager.setCacheManagerConfigFile("classpath:ehcache.xml"); return cacheManager; } }

6、controller

1)、登陆类LoginController,这个类有个测试方法直接生成可以使用的token

/** * 登录接口类 */@RestControllerpublic class LoginController {    @Resource    private UserService userService;    private static final Logger _logger = LoggerFactory.getLogger(LoginController.class);    @PostMapping("/login")    public BaseResponse
login(User user) { _logger.info("用户请求登录获取Token"); String username = user.getUsername(); // admin String password = user.getPassword(); // 123456 UserInfo userinfo = userService.findByUsername(username); //随机数盐 String salt = userinfo.getSalt(); //原密码加密(通过username + salt作为盐) String encodedPassword = ShiroUtil.md5(password, salt); if (userinfo.getPassword().equals(encodedPassword)) { String token = JWTUtil.sign(username, encodedPassword); //eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MDk2NzMyMzMsInVzZXJuYW1lIjoiYWRtaW4ifQ.L_8-WmXOSk1_wZvZiOq2OkgTMgbPEIOAz7RVT47iqK8 return new BaseResponse<>(true, "Login success", token); } else { throw new UnauthorizedException(); } } /** * 这个方法用来快速生成登陆的token。 */ @Test public void test() { String username = "admin"; // admin String password = "123456"; // 123456 UserInfo userinfo = new UserService().findByUsername(username); String salt = userinfo.getSalt(); String encodedPassword = ShiroUtil.md5(password, salt); if (userinfo.getPassword().equals(encodedPassword)) { String token = JWTUtil.sign(username, encodedPassword); System.out.println(token); } } }

2)PublicController Api接口类。(这里注意,我们在jwt拦截器是全部放行的,但是如果没带header头,即没有登陆的情况下,使用了@RequiresAuthentication注解控制权限。)

/** * API接口类 */@RestController@RequestMapping(value = "/api/v1")public class PublicController {    private static final Logger _logger = LoggerFactory.getLogger(PublicController.class);    /**     * hello接口     */    @RequestMapping(value = "/hello", method = RequestMethod.GET)    @RequiresAuthentication    public BaseResponse hello() {        _logger.info("hello接口 start...");        BaseResponse result = new BaseResponse<>();        result.setSuccess(true);        result.setMsg("已入网并绑定了网点");        return result;    }}

三、测试

1、调用login接口中的test方法,得到下面token。(使用效果和login登陆一样)

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MDk2ODcxNzcsInVzZXJuYW1lIjoiYWRtaW4ifQ.60MAVxKU3iG8KS0ccYZExjhuJM0-9U2cS29RcklmPlE

2、我再用apiPost发送请求

 

转载地址:http://hhuni.baihongyu.com/

你可能感兴趣的文章
139. Word Break (DP)
查看>>
Tensorflow入门资料
查看>>
剑指_用两个栈实现队列
查看>>
剑指_顺时针打印矩阵
查看>>
剑指_栈的压入弹出序列
查看>>
剑指_复杂链表的复制
查看>>
服务器普通用户(非管理员账户)在自己目录下安装TensorFlow
查看>>
星环后台研发实习面经
查看>>
大数相乘不能用自带大数类型
查看>>
字节跳动后端开发一面
查看>>
CentOS Tensorflow 基础环境配置
查看>>
centOS7安装FTP
查看>>
FTP的命令
查看>>
CentOS操作系统下安装yum的方法
查看>>
ping 报name or service not known
查看>>
FTP 常见问题
查看>>
zookeeper单机集群安装
查看>>
do_generic_file_read()函数
查看>>
Python学习笔记之数据类型
查看>>
Python学习笔记之特点
查看>>