I.はじめに
なぜ史郎?
Apacheの史郎は、強力でJavaのセキュリティフレームワークを使用して簡単です。史郎を使用している開発者は簡単に認証、許可、パスワード、およびセッション管理を完了することができます。
史郎の主なAPI
認証:認証/ログインは、ユーザが適切なIDを持っていないことを確認してください。
認証:能力を検証することを許可、ユーザーが権限を認証したことを確認し、ユーザーがそのような一般的なようなことを行うことができるかどうか、である:ユーザーが役割を持っていることを確認してください。またはきめ細かいユーザーがリソースに対するアクセス権を持っているかどうかを確認します。
Managerセッション:あるセッション管理は、1つのセッションでユーザがログインした後、終了が存在しない場合には、セッション内のすべての情報です。セッションは通常のJava SE環境であることができ、それは、Web環境としてすることができ;
暗号化:代わりに平文を格納するようにデータベースに格納された暗号化パスワードなどの暗号化、セキュアなデータ、、;
サポートウェブ: Webサポートは非常に簡単にウェブ環境に統合することができます。
キャッシュ:キャッシュをユーザーが役割と、そのユーザーの情報、ログインする、などの/パーミッションが、これは効率を高めることができるたびにチェックする必要はありません。
同時実行:史郎サポート同時検証は、このようなスレッド内の別のスレッドである開口部などのアプリケーションを、マルチスレッド、アクセス権は自動的に過去に伝播することができます。
テスト:テスト・サポートを提供します。
実行するのは:ユーザーが別のユーザー(彼らが許可した場合)アクセスのアイデンティティにふりをすることができます。
私は覚えておいてください:最初のログイン後、次の時間が記録され戻ってきていないつまり、これは非常に共通の特徴である、私を忘れないでください。
どのよう史郎動作しますか?
件名: ;抽象的な概念であり、対象は、それが現在の「ユーザー」は、ユーザーが必ずしも特定の人物ではなく、任意の現在のものは、そのようなウェブクローラ、ロボットなどの件名対話型アプリケーション、あるを表し、すべての件名にバインドされていますSecurityManagerを、件名を持つすべての相互作用は、セキュリティマネージャに委託されます。件名は、ファサードと考えることができ、セキュリティマネージャは、実際の演奏です。
セキュリティマネージャ:セキュリティマネージャ、すべての安全関連の操作がSecurityManagerをと相互作用する、であり、それはすべての件名を管理し、我々はそれを見ることができますが、勉強している場合SpringMVCの導入の背後にある他のコンポーネントとの相互作用を担当する、史郎の中核でありますあなたはDispatcherServletのフロントコントローラとしてそれを見ることができます。
レルム:ドメインは、四郎が認証ユーザにセキュリティマネージャであるレルムから(例えば、ユーザ、ロール、パーミッションなど)安全性データから取得し、それはレルムから適切なユーザは、ユーザの身元が正当なものであるかを決定するために比較される取得する必要がある。レルムから必要データソースとしてCANレルム、すなわち安全なデータソース、役割/権限は、ユーザが操作することができることを確認するようにユーザに与えます。
II。コアコード
1.ログイン/ログアウト機能
私たちは二人のユーザーを持っていると仮定します。
ADMIN1:成功、アイデンティティとセッションIDのユーザー名/パスワードのADMIN1 /管理;
ADMIN2:成功、アイデンティティとセッションIDのユーザー名/パスワードのADMIN2 /管理;
HTTPで:必要なアクセス制御インタフェース後ヘッダヘッダテープセッションID(セッションID形式認可)を要求しなければなりません。
ログイン/ログアウトコード:
@PostMapping("/login")
public Object login(@RequestBody LoginVo loginVo) {
//得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(loginVo.getAccount(), loginVo.getPassword());
token.setRememberMe(false);
if (subject.isAuthenticated()) {
subject.logout();
}
try {
//登录,即身份验证
subject.login(token);
Session session = subject.getSession();
User user = User.loginUser();
user.setFlag(loginVo.getFlag());
user.setSessionId(session.getId());
//返回一个sessionId
return user;
} catch (UnknownAccountException e){
return "账号/密码错误";
}
catch (AuthenticationException e) {
//身份验证失败
return "程序错误";
}
}
@PostMapping("/logout")
public Object logout() {
try {
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "成功退出登录!";
} catch (Exception e) {
return "退出登录失败!";
}
}
2.Shiro配置
史郎構成されたインターセプター、およびオープン@RequiresPermissionsのアノテーションのサポート
package com.lee.config;
import com.lee.filter.ApiPathPermissionFilter;
import com.lee.shiro.MyRealm;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;
/**
*
* 功能描述: shiro配置类
*
* @param:
* @return:
* @auther: liyiyu
* @date: 2020/3/17 17:05
*/
@Configuration
public class ShiroConfig {
/**
* 设置/login /logout 两个请求可以任意访问
*/
@Bean
public ShiroFilterFactoryBean factory(SecurityManager securityManager) {
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
factoryBean.setSecurityManager(securityManager);
// 自定义拦截器
Map<String, Filter> filterMap = new LinkedHashMap<>();
filterMap.put("apiPathPermissionFilter", new ApiPathPermissionFilter());
factoryBean.setFilters(filterMap);
Map<String, String> filterRuleMap = new LinkedHashMap<>();
//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterRuleMap.put("/logout", "logout");
// 配置不会被拦截的链接 顺序判断
filterRuleMap.put("/login", "anon");
// 其他请求通过我们自己的apiPathPermissionFilter
filterRuleMap.put("/*", "apiPathPermissionFilter");
filterRuleMap.put("/**", "apiPathPermissionFilter");
filterRuleMap.put("/*.*", "apiPathPermissionFilter");
factoryBean.setFilterChainDefinitionMap(filterRuleMap);
return factoryBean;
}
/**
* SecurityManager安全管理器,是Shiro的核心
*/
@Bean
public SecurityManager securityManager(MyRealm myRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置session生命周期
securityManager.setSessionManager(sessionManager());
// 设置自定义 realm
securityManager.setRealm(myRealm);
return securityManager;
}
/**
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
* 配置以下两个bean(DefaultAdvisorAutoProxyCreator和AuthorizationAttributeSourceAdvisor)即可实现此功能
*/
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
// 强制使用cglib,防止重复代理和可能引起代理出错的问题
// https://zhuanlan.zhihu.com/p/29161098
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
/**
* 开启aop注解支持
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
/**
* 自定义sessionManager
*/
@Bean
public SessionManager sessionManager() {
//由shiro管理session,每次访问后会重置过期时间
MySessionManager mySessionManager = new MySessionManager();
//设置过期时间,单位:毫秒
mySessionManager.setGlobalSessionTimeout(MySessionManager.DEFAULT_GLOBAL_SESSION_TIMEOUT);
return mySessionManager;
}
/**
* LifecycleBeanPostProcessor将Initializable和Destroyable的实现类统一在其内部,
* 自动分别调用了Initializable.init()和Destroyable.destroy()方法,从而达到管理shiro bean生命周期的目的
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
}
2.sessionId GET
セッションマネージャの独自のセット、DefaultWebSessionManagerを書き換える必要性を実現
package com.lee.config;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;
/**
*
* 功能描述: 自定义sessionId获取,用于请求头中传递sessionId,并让shiro获取判断权限
* 想实现自己的一套session管理器,需继承DefaultWebSessionManager来重写
*
* @param:
* @return:
* @auther: liyiyu
* @date: 2020/3/17 17:05
*/
public class MySessionManager extends DefaultWebSessionManager {
private static final String AUTHORIZATION = "Authorization";
public MySessionManager() {
super();
}
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
//修改shiro管理sessionId的方式,改为获取请求头,前端时header必须带上 Authorization:sessionId
return WebUtils.toHttp(request).getHeader(AUTHORIZATION);
}
}
3.ログを達成し、アクセス制御
doGetAuthenticationInfoは、データベースのパスワードと暗号化がアカウントと一致照会することによって、裁判官への具体的な実装のログインが正しいです
doGetAuthorizationInfo許可判定は、現在のユーザーのアクセス権を取得することにより、ユーザの要求を決定するために実施されています
私たちは、ADMIN2がpoetry3のpoetry4権を持っている、のADMIN1 poetry1のpoetry2権限を設定しています
package com.lee.shiro;
import com.lee.entity.User;
import com.lee.util.PasswordUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.*;
/**
*
* 功能描述:自定义用户认证授权类
*
* @param:
* @return:
* @auther: liyiyu
* @date: 2020/3/17 17:04
*/
@Component
public class MyRealm extends AuthorizingRealm {
@Value("${password_salt}")
private String salt;
/**
* AuthorizationInfo 用于聚合授权信息
* 会判断@RequiresPermissions 里的值是否
*
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//进入数据库查询拥有的权限查询
List<String> list = new ArrayList<>();
User user = (User)SecurityUtils.getSubject().getPrincipal();
if ("1".equals(user.getRoleId())){
String[] array = {"poetry1","poetry2"};
list = Arrays.asList(array);
}else if ("2".equals(user.getRoleId())){
String[] array = {"poetry3","poetry4"};
list = Arrays.asList(array);
}
Set<String> set = new HashSet(list);
info.addStringPermissions(set);
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String account = (String) authenticationToken.getPrincipal(); //得到用户名
String pwd = new String((char[]) authenticationToken.getCredentials()); //得到密码
//将密码进行加密处理,与数据库加密后的密码进行比较
String inPasswd = PasswordUtils.entryptPasswordWithSalt(pwd, salt);
//通过数据库验证账号密码,成功的话返回一个封装的ShiroUser实例
String saltPasswd = PasswordUtils.entryptPasswordWithSalt("admin", salt);
User user = null;
//这里要注意返回用户信息尽可能少,返回前端所需要的用户信息就可以了
if ("admin1".equals(account) && saltPasswd.equals(inPasswd)) {
user = new User();
user.setUid("1");
user.setUname("用户一");
user.setEid(1);
user.setDeptName("祖安大区");
user.setDeptId("1");
user.setRoleId("1");
user.setRoleName("祖安文科状元");
}else if ("admin2".equals(account) && saltPasswd.equals(inPasswd)){
user = new User();
user.setUid("1");
user.setUname("用户二");
user.setEid(1);
user.setDeptName("祖安大区");
user.setDeptId("1");
user.setRoleId("2");
user.setRoleName("祖安理科状元");
}
if (user != null) {
//如果身份认证验证成功,返回一个AuthenticationInfo实现;
return new SimpleAuthenticationInfo(user, pwd, getName());
} else {
//错误的帐号
throw new UnknownAccountException();
}
}
}
4.クロスドメインの問題
開発プロセスのクロスドメインの問題の出現
package com.lee.config;
import com.lee.interceptor.UrlInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
/**
*
* 功能描述: MVC拦截器配置
*
* @param:
* @return:
* @auther: liyiyu
* @date: 2020/3/17 17:06
*/
@Configuration
public class MvcConfiguration extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//为所有请求处理跨域问题
registry.addInterceptor(new UrlInterceptor()).addPathPatterns("/**");
super.addInterceptors(registry);
}
}
クロスドメインソリューション
package com.lee.interceptor;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
*
* 功能描述: url拦截器,处理前后端分离跨域问题
*
* @param:
* @return:
* @auther: liyiyu
* @date: 2020/3/17 17:04
*/
public class UrlInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
//允许跨域,不能放在postHandle内
response.setHeader("Access-Control-Allow-Credentials", "true");
String str = request.getHeader("origin");
response.setHeader("Access-Control-Allow-Origin", request.getHeader("origin"));
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, DELETE, PUT");
response.setHeader("Access-Control-Max-Age", "0");
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept,Authorization,WG-App-Version, WG-Device-Id, WG-Network-Type, WG-Vendor, WG-OS-Type, WG-OS-Version, WG-Device-Model, WG-CPU, WG-Sid, WG-App-Id, WG-Token");
response.setHeader("XDomainRequestAllowed", "1");
return true;
}
}
アクセス許可を確認します。5.
権限@RequiresPermissionsを確認するには(ShiroConfigクラスコンフィギュレーション)
@GetMapping("/poetry1")
@RequiresPermissions("poetry1")
public Object poetry1(){
return "床前明月光";
}
@GetMapping("/poetry2")
@RequiresPermissions("poetry2")
public Object poetry2(){
return "疑是地上霜";
}
@GetMapping("/poetry3")
@RequiresPermissions("poetry3")
public Object poetry3(){
return "举头望明月";
}
@GetMapping("/poetry4")
@RequiresPermissions("poetry4")
public Object poetry4(){
return "低头思故乡";
}