使用场景:
- 移动端通过业务系统鉴权
- 移动端免登录(登录一次以后)
解决方案:
疑问
当然我们这章是讲JWT,那么会有以下的疑问:
若服务端已经接入了SSO,那么在移动端用户登录信息提交给SSO还是服务端?(毫无疑问是服务端,SSO对于移动端必须是透明的)
若采用无会话方式,如何获取token,服务端如何鉴权?
1.提交用户名密码到服务端,服务端把数据给到sso,sso最终返回用户数据
2. 根据用户数据创建token返回给移动端
3. 移动端登录后请求都带token给到服务端鉴权
在使用token鉴权的情况下,退出如何解决?(客户端丢弃token即可)
鉴权流程
我们再讲一下整一个鉴权流程
配置要素
重点:sso必须支持rest认证方式
<dependency>
<groupId>io.buji</groupId>
<artifactId>buji-pac4j</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.pac4j</groupId>
<artifactId>pac4j-cas</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.pac4j</groupId>
<artifactId>pac4j-jwt</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.pac4j</groupId>
<artifactId>pac4j-http</artifactId>
<version>2.1.0</version>
</dependency>
鉴权配置
若马上看下面的代码,估计一时半会看不懂,所以必须再讲一下整个交易过程
服务端鉴权过程有两个个角色分别为,Shiro、Pac4j,那他们的职责是什么?
Shiro:
判断当前Subject是否有权限执行该资源,所以他的核心是Realm、Filter,只有被过滤到的资源才会走到Realm
Pac4j:
1. JWTAuthenticator对token进行鉴别
2. CasRestFormClient 支持通过rest接口传入用户名密码进行对sso进行认证获取UserProfile
3. Pac4jRealm鉴权的realm
4. SubjectFactory需要调整成Pac4jSubjectFactory
ShiroConfiguration.java
/* * 版权所有.(c)2008-2017. 卡尔科技工作室 */ package com.carl.wolf.permission.config; import io.buji.pac4j.filter.CallbackFilter; import io.buji.pac4j.filter.LogoutFilter; import io.buji.pac4j.filter.SecurityFilter; import io.buji.pac4j.realm.Pac4jRealm; import io.buji.pac4j.subject.Pac4jSubjectFactory; import org.apache.shiro.mgt.DefaultSecurityManager; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.mgt.SubjectFactory; import org.apache.shiro.realm.Realm; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.spring.web.config.AbstractShiroWebFilterConfiguration; import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition; import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition; import org.pac4j.cas.client.CasClient; import org.pac4j.cas.client.rest.CasRestFormClient; import org.pac4j.cas.config.CasConfiguration; import org.pac4j.cas.config.CasProtocol; import org.pac4j.core.client.Clients; import org.pac4j.core.config.Config; import org.pac4j.http.client.direct.ParameterClient; import org.pac4j.jwt.config.encryption.SecretEncryptionConfiguration; import org.pac4j.jwt.config.signature.SecretSignatureConfiguration; import org.pac4j.jwt.credentials.authenticator.JwtAuthenticator; import org.pac4j.jwt.profile.JwtGenerator; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.servlet.Filter; import java.util.HashMap; import java.util.Map; /** * 对shiro的安全配置,是对cas的登录策略进行配置 * * @author Carl * @date 2017/9/16 * @since 1.0.0 */ @Configuration public class ShiroConfiguration extends AbstractShiroWebFilterConfiguration {
@Value("#{ @environment['cas.prefixUrl'] ?: null }") private String prefixUrl; @Value("#{ @environment['cas.loginUrl'] ?: null }") private String casLoginUrl; @Value("#{ @environment['cas.callbackUrl'] ?: null }") private String callbackUrl; //jwt秘钥 @Value("${jwt.salt}") private String salt; @Bean public Realm pac4jRealm() { return new Pac4jRealm(); } /** * cas核心过滤器,把支持的client写上,filter过滤时才会处理,clients必须在casConfig.clients已经注册 * * @return */ @Bean public Filter casSecurityFilter() { SecurityFilter filter = new SecurityFilter(); filter.setClients("CasClient,rest,jwt"); filter.setConfig(casConfig()); return filter; } /** * JWT Token 生成器,对CommonProfile生成然后每次携带token访问 * @return */ @Bean protected JwtGenerator jwtGenerator() { return new JwtGenerator(new SecretSignatureConfiguration(salt), new SecretEncryptionConfiguration(salt)); } /** * 通过rest接口可以获取tgt,获取service ticket,甚至可以获取CasProfile * @return */ @Bean protected CasRestFormClient casRestFormClient() { CasRestFormClient casRestFormClient = new CasRestFormClient(); casRestFormClient.setConfiguration(casConfiguration()); casRestFormClient.setName("rest"); return casRestFormClient; } @Bean protected Clients clients() { //可以设置默认client Clients clients = new Clients(); //token校验器,可以用HeaderClient更安全 ParameterClient parameterClient = new ParameterClient("token", jwtAuthenticator()); parameterClient.setSupportGetRequest(true); parameterClient.setName("jwt"); //支持的client全部设置进去 clients.setClients(casClient(), casRestFormClient(), parameterClient); return clients; } /** * JWT校验器,也就是目前设置的ParameterClient进行的校验器,是rest/或者前后端分离的核心校验器 * @return */ @Bean protected JwtAuthenticator jwtAuthenticator() { JwtAuthenticator jwtAuthenticator = new JwtAuthenticator(); jwtAuthenticator.addSignatureConfiguration(new SecretSignatureConfiguration(salt)); jwtAuthenticator.addEncryptionConfiguration(new SecretEncryptionConfiguration(salt)); return jwtAuthenticator; } @Bean protected Config casConfig() { Config config = new Config(); config.setClients(clients()); return config; } /** * cas的基本设置,包括或url等等,rest调用协议等 * @return */ @Bean public CasConfiguration casConfiguration() { CasConfiguration casConfiguration = new CasConfiguration(casLoginUrl); casConfiguration.setProtocol(CasProtocol.CAS30); casConfiguration.setPrefixUrl(prefixUrl); return casConfiguration; } @Bean public CasClient casClient() { CasClient casClient = new CasClient(); casClient.setConfiguration(casConfiguration()); casClient.setCallbackUrl(callbackUrl); return casClient; } /** * 路径过滤设置 * @return */ @Bean public ShiroFilterChainDefinition shiroFilterChainDefinition() { DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition(); definition.addPathDefinition("/callback", "callbackFilter"); definition.addPathDefinition("/logout", "logoutFilter"); definition.addPathDefinition("/**", "casSecurityFilter"); return definition; } /** * 由于cas代理了用户,所以必须通过cas进行创建对象 * * @return */ @Bean protected SubjectFactory subjectFactory() { return new Pac4jSubjectFactory(); } /** * 对过滤器进行调整 * * @param securityManager * @return */ @Bean protected ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { //把subject对象设为subjectFactory ((DefaultSecurityManager) securityManager).setSubjectFactory(subjectFactory()); ShiroFilterFactoryBean filterFactoryBean = super.shiroFilterFactoryBean(); filterFactoryBean.setSecurityManager(securityManager); filterFactoryBean.setFilters(filters()); return filterFactoryBean; } /** * 对shiro的过滤策略进行明确 * @return */ @Bean protected Map<String, Filter> filters() { //过滤器设置 Map<String, Filter> filters = new HashMap<>(); filters.put("casSecurityFilter", casSecurityFilter()); CallbackFilter callbackFilter = new CallbackFilter(); callbackFilter.setConfig(casConfig()); filters.put("callbackFilter", callbackFilter); LogoutFilter logoutFilter = new LogoutFilter(); logoutFilter.setConfig(casConfig()); filters.put("logoutFilter", logoutFilter); return filters; } }
token生成
@RequestMapping("/user/login") public Object login(HttpServletRequest request, HttpServletResponse response) { Map<String, Object> model = new HashMap<>(); J2EContext context = new J2EContext(request, response); final ProfileManager<CasRestProfile> manager = new ProfileManager(context); final Optional<CasRestProfile> profile = manager.get(true); //获取ticket TokenCredentials tokenCredentials = casRestFormClient.requestServiceTicket(serviceUrl, profile.get(), context); //根据ticket获取用户信息 final CasProfile casProfile = casRestFormClient.validateServiceTicket(serviceUrl, tokenCredentials, context); //生成jwt token String token = generator.generate(casProfile); model.put("token", token); return new HttpEntity<>(model); }
本文来自博客园,作者:洛神灬殇,转载请注明原文链接:https://www.cnblogs.com/liboware/p/12512574.html,任何足够先进的科技,都与魔法无异。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/70240.html