若是您有幸能看到,請認閱讀如下內容;javascript
一、本項目臨摹自abel533的Guns,他的項目 fork 自 stylefeng 的 Guns!開源的世界真好,能夠學到不少知識。html
二、版權歸原做者全部,本身只是學習使用。跟着大佬的思路,但願本身也能變成大佬。gogogo》。。java
三、目前只是一個後臺模塊,但願本身技能加強到必定時,能夠把stylefeng 的 [Guns]融合進來。git
四、note裏面是本身的學習過程,菜鳥寫的,不是大佬寫的。內容都是大佬的。github
五、若有拼寫錯誤,還請見諒。目前的桌子不適合打字,本文只爲本身記錄.web
問你們一個問題,大家在看本文的時候,以爲哪裏有須要修改的地方?內容和格式方面,歡迎你們提出來。redis
若是您有幸能看到,請認閱讀如下內容;算法
一、本項目臨摹自abel533的Guns,他的項目 fork 自 stylefeng 的 Guns!開源的世界真好,能夠學到不少知識。spring
二、版權歸原做者全部,本身只是學習使用。跟着大佬的思路,但願本身也能變成大佬。gogogo》。。api
三、目前只是一個後臺模塊,但願本身技能加強到必定時,能夠把stylefeng 的 [Guns]融合進來。
四、note裏面是本身的學習過程,菜鳥寫的,不是大佬寫的。內容都是大佬的。
在本項目中使用了Apache 的Shiro爲安全護航,其實Spring也提供了聲明式安全保護的框架,那就是Spring Securitu。有興趣的能夠看下我以前的筆記Srping-Secutiry實戰筆記。
(1)、Spring Security 是基於Spring 應用程序提供的聲明式安全保護的安全框架。Spring Sercurity 提供了完整的安全性解決方案,它可以在Web請求級別和方法調用級別處理身份認證和受權,由於是基於Spring,因此Spring Security充分利用了依賴注入(Dependency injection DI) 和麪向切面的技術。
Spring Security從兩個角度來解決安全性,他使用Servlet規範中的Filter保護Web請求並限制URL級別的訪問。Spring Security還可以使用AOP保護方法調用——藉助於對象代理和使用通知,可以取保只有具有適當權限的用戶才能訪問安全保護的方法。
(2)、Apache Shiro是一個功能強大且靈活的開源安全框架,主要功能包括用戶認證、受權、會話管理以及加密。
Apache Shiro的首要目標是易於使用和理解。系統安全是很是複雜甚至痛苦的,但Shiro並非。一個框架應該儘量的隱藏那些複雜的細節,而且公開一組簡潔直觀的API以簡化開發人員在系統安全上所付出的努力。
我的傾向於前者,畢竟是Spring生態系統上的一員。
Apache Shiro功能:
Apache Shiro的特色: 參考這裏
這些特色被Shiro開發團隊稱之爲「應用安全的四大基石」——認證、受權、會話管理和加密:
看完這些基礎的概念,咱們直接看接口的定義吧。
/** * 定義shirorealm所需數據的接口 */
public interface IShiro {
/** * 根據帳號獲取登陸用戶 */
User user(String account);
/** * 根據系統用戶獲取Shiro的用戶 */
ShiroUser shiroUser(User user);
//省略部分
/** * 獲取shiro的認證信息 */
SimpleAuthenticationInfo info(ShiroUser shiroUser, User user, String realmName);
}
--------------------------------------------------------------------------------
/** * 自定義Authentication對象,使得Subject除了攜帶用戶的登陸名外還能夠攜帶更多信息 */
public class ShiroUser implements Serializable {
private static final long serialVersionUID = 1L;
public Integer id; // 主鍵ID
public String account; // 帳號
public String name; // 姓名
public Integer deptId; // 部門id
public List<Integer> roleList; // 角色集
public String deptName; // 部門名稱
public List<String> roleNames; // 角色名稱集
//Setter、Getter略
複製代碼
ShiroFactroy工廠,SpringContextHolder
是Spring的ApplicationContext的持有者,能夠用靜態方法的方式獲取spring容器中的bean
@Service
@DependsOn("springContextHolder")
@Transactional(readOnly = true)
public class ShiroFactroy implements IShiro {
@Autowired
private UserMapper userMapper;
@Autowired
private MenuMapper menuMapper;
public static IShiro me() {
return SpringContextHolder.getBean(IShiro.class);
}
@Override
public User user(String account) {
User user = userMapper.getByAccount(account);
// 帳號不存在
if (null == user) {
throw new CredentialsException();
}
// 帳號被凍結
if (user.getStatus() != ManagerStatus.OK.getCode()) {
throw new LockedAccountException();
}
return user;
}
@Override
public ShiroUser shiroUser(User user) {
ShiroUser shiroUser = new ShiroUser();
shiroUser.setId(user.getId()); // 帳號id
shiroUser.setAccount(user.getAccount());// 帳號
shiroUser.setDeptId(user.getDeptid()); // 部門id
shiroUser.setDeptName(ConstantFactory.me().getDeptName(user.getDeptid()));// 部門名稱
shiroUser.setName(user.getName()); // 用戶名稱
Integer[] roleArray = Convert.toIntArray(user.getRoleid());// 角色集合
List<Integer> roleList = new ArrayList<Integer>();
List<String> roleNameList = new ArrayList<String>();
for (int roleId : roleArray) {
roleList.add(roleId);
roleNameList.add(ConstantFactory.me().getSingleRoleName(roleId));
}
shiroUser.setRoleList(roleList);
shiroUser.setRoleNames(roleNameList);
return shiroUser;
}
//省略部分
@Override
public SimpleAuthenticationInfo info(ShiroUser shiroUser, User user, String realmName) {
String credentials = user.getPassword();
// 密碼加鹽處理
String source = user.getSalt();
ByteSource credentialsSalt = new Md5Hash(source);
return new SimpleAuthenticationInfo(shiroUser, credentials, credentialsSalt, realmName);
}
}
複製代碼
這個類是重點
public class ShiroDbRealm extends AuthorizingRealm {
/** * 登陸認證 */
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
IShiro shiroFactory = ShiroFactroy.me();
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
User user = shiroFactory.user(token.getUsername());
ShiroUser shiroUser = shiroFactory.shiroUser(user);
SimpleAuthenticationInfo info = shiroFactory.info(shiroUser, user, super.getName());
return info;
}
-------------------------------------------------------------------------------------------------
/** * 權限認證 */
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
IShiro shiroFactory = ShiroFactroy.me();
ShiroUser shiroUser = (ShiroUser) principals.getPrimaryPrincipal();
List<Integer> roleList = shiroUser.getRoleList();
Set<String> permissionSet = new HashSet<>();
Set<String> roleNameSet = new HashSet<>();
for (Integer roleId : roleList) {
List<String> permissions = shiroFactory.findPermissionsByRoleId(roleId);
if (permissions != null) {
for (String permission : permissions) {
if (ToolUtil.isNotEmpty(permission)) {
permissionSet.add(permission);
}
}
}
String roleName = shiroFactory.findRoleNameByRoleId(roleId);
roleNameSet.add(roleName);
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermissions(permissionSet);
info.addRoles(roleNameSet);
return info;
}
-------------------------------------------------------------------------------------
/** * 設置認證加密方式 */
@Override
public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
HashedCredentialsMatcher md5CredentialsMatcher = new HashedCredentialsMatcher();
md5CredentialsMatcher.setHashAlgorithmName(ShiroKit.hashAlgorithmName);
md5CredentialsMatcher.setHashIterations(ShiroKit.hashIterations);
super.setCredentialsMatcher(md5CredentialsMatcher);
}
}
複製代碼
讓咱們來具體看看怎麼用?權限是設置好了,可是每次用的時候須要檢查是否擁有權限
/** * 檢查用接口 */
public interface ICheck {
/** * 檢查指定角色 */
boolean check(Object[] permissions);
/** * 檢查全體角色 */
boolean checkAll();
}
--------------------------------------------------------------------------------
/** * 權限自定義檢查 */
@Service
@DependsOn("springContextHolder")
@Transactional(readOnly = true)
public class PermissionCheckFactory implements ICheck {
public static ICheck me() {
return SpringContextHolder.getBean(ICheck.class);
}
@Override
public boolean check(Object[] permissions) {
ShiroUser user = ShiroKit.getUser();
if (null == user) {
return false;
}
String join = CollectionKit.join(permissions, ",");
if (ShiroKit.hasAnyRoles(join)) {
return true;
}
return false;
}
public boolean checkAll() {...}
}
複製代碼
/** * 權限檢查工廠 */
public class PermissionCheckManager {
private final static PermissionCheckManager me = new PermissionCheckManager();
private ICheck defaultCheckFactory = SpringContextHolder.getBean(ICheck.class);
public static PermissionCheckManager me() {
return me;
}
//....
public static boolean check(Object[] permissions) {
return me.defaultCheckFactory.check(permissions);
}
public static boolean checkAll() {
return me.defaultCheckFactory.checkAll();
}
}
複製代碼
這時候就須要把權限設置爲一個切面,在須要的時候直接織入。
/** * 權限註解,用於檢查權限 規定訪問權限 */
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Permission {
String[] value() default {};
}
-------------------------------------------------------------------------------
/** * AOP 權限自定義檢查 */
@Aspect
@Component
public class PermissionAop {
@Pointcut(value = "@annotation(com.guo.guns.common.annotion.Permission)")
private void cutPermission() {
}
@Around("cutPermission()")
public Object doPermission(ProceedingJoinPoint point) throws Throwable {
MethodSignature ms = (MethodSignature) point.getSignature();
Method method = ms.getMethod();
Permission permission = method.getAnnotation(Permission.class);
Object[] permissions = permission.value();
if (permissions == null || permissions.length == 0) {
//檢查全體角色
boolean result = PermissionCheckManager.checkAll();
if (result) {
return point.proceed();
} else {
throw new NoPermissionException();
}
} else {
//檢查指定角色
boolean result = PermissionCheckManager.check(permissions);
if (result) {
return point.proceed();
} else {
throw new NoPermissionException();
}
}
}
}
複製代碼
讓咱們看一下在代碼中具體是如何使用的,只有具備管理員才具有修改的權限。
/** * 管理員角色的名字 */
String ADMIN_NAME = "administrator";
--------------------------------------------------------------------------------
/** * 角色修改 */
@RequestMapping(value = "/edit")
@BussinessLog(value = "修改角色", key = "name", dict = Dict.RoleDict)
@Permission(Const.ADMIN_NAME)
@ResponseBody
public Tip edit(@Valid Role role, BindingResult result) {
if (result.hasErrors()) {
throw new BussinessException(BizExceptionEnum.REQUEST_NULL);
}
roleMapper.updateByPrimaryKeySelective(role);
//刪除緩存
CacheKit.removeAll(Cache.CONSTANT);
return SUCCESS_TIP;
}
複製代碼
以前處理Session的辦法是將HTTP session狀態保存在獨立的數據存儲中,這個存儲位於運行應用程序代碼的JVM以外。使用 tomcat-redis-session-manager 開源項目解決分佈式session跨域的問題,他的主要思想是利用Servlet容器提供的插件功能,自定義HttpSession的建立和管理策略,並經過配置的方式替換掉默認的策略。使用過tomcat-redis-session-manager 的都應該知道,配置相對仍是有一點繁瑣的,須要人爲的去修改Tomcat的配置,須要耦合Tomcat等Servlet容器的代碼,而且對於分佈式Redis集羣的管理並非很好,與之相對的我的認爲比較好的一個框架Spring Session能夠真正對用戶透明的去管理分佈式Session。參考
Spring Session提供了一套建立和管理Servlet HttpSession的方案。Spring Session提供了集羣Session(Clustered Sessions)功能,默認採用外置的Redis來存儲Session數據,以此來解決Session共享的問題。
Spring Session不依賴於Servlet容器,而是Web應用代碼層面的實現,直接在已有項目基礎上加入spring Session框架來實現Session統一存儲在Redis中。若是你的Web應用是基於Spring框架開發的,只須要對現有項目進行少許配置,便可將一個單機版的Web應用改成一個分佈式應用,因爲不基於Servlet容器,因此能夠隨意將項目移植到其餘容器。
/** * spring session配置 */
//@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800) //session過時時間 若是部署多機環境,須要打開註釋
@ConditionalOnProperty(prefix = "guns", name = "spring-session-open", havingValue = "true")
public class SpringSessionConfig {
}
複製代碼
由於是聲明式的,因此用起來很簡單
/** * 靜態調用session的攔截器 */
@Aspect
@Component
public class SessionInterceptor extends BaseController {
@Pointcut("execution(* com.guo.guns.*..controller.*.*(..))")
public void cutService() {
}
@Around("cutService()")
public Object sessionKit(ProceedingJoinPoint point) throws Throwable {
HttpSessionHolder.put(super.getHttpServletRequest().getSession());
try {
return point.proceed();
} finally {
HttpSessionHolder.remove();
}
}
}
--------------------------------------------------------------------------------
/** * 驗證session超時的攔截器 * * @author fengshuonan * @date 2017年6月7日21:08:48 */
@Aspect
@Component
@ConditionalOnProperty(prefix = "guns", name = "session-open", havingValue = "true")
public class SessionTimeoutInterceptor extends BaseController {
@Pointcut("execution(* com.guo.guns.*..controller.*.*(..))")
public void cutService() {
}
@Around("cutService()")
public Object sessionTimeoutValidate(ProceedingJoinPoint point) throws Throwable {
String servletPath = HttpKit.getRequest().getServletPath();
if (servletPath.equals("/kaptcha") || servletPath.equals("/login") || servletPath.equals("/global/sessionError")) {
return point.proceed();
}else{
if(ShiroKit.getSession().getAttribute("sessionFlag") == null){
ShiroKit.getSubject().logout();
throw new InvalidSessionException();
}else{
return point.proceed();
}
}
}
}
複製代碼
讓咱們看下具體是如何使用的》
/** * 獲取shiro指定的sessionKey * */
@SuppressWarnings("unchecked")
public static <T> T getSessionAttr(String key) {
Session session = getSession();
return session != null ? (T) session.getAttribute(key) : null;
}
/** * 設置shiro指定的sessionKey * */
public static void setSessionAttr(String key, Object value) {
Session session = getSession();
session.setAttribute(key, value);
}
/** * 移除shiro指定的sessionKey */
public static void removeSessionAttr(String key) {
Session session = getSession();
if (session != null)
session.removeAttribute(key);
}
-------------------登陸執行中的步驟-----------------------------------------
ShiroUser shiroUser = ShiroKit.getUser();
super.getSession().setAttribute("shiroUser", shiroUser);
super.getSession().setAttribute("username", shiroUser.getAccount());
LogManager.me().executeLog(LogTaskFactory.loginLog(shiroUser.getId(), getIp()));
ShiroKit.getSession().setAttribute("sessionFlag",true);
return REDIRECT + "/";
複製代碼
super.getSession()調用的是BaseController中的方法。
防止XSS攻擊,經過XssFilter類對全部的輸入的非法字符串進行過濾以及替換。
public class XssFilter implements Filter {
FilterConfig filterConfig = null;
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}
public void destroy() {
this.filterConfig = null;
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
chain.doFilter(new XssHttpServletRequestWrapper(
(HttpServletRequest) request), response);
}
}
---------------------------------------------------------------------------------
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
public XssHttpServletRequestWrapper(HttpServletRequest servletRequest) {
super(servletRequest);
}
public String[] getParameterValues(String parameter) {
String[] values = super.getParameterValues(parameter);
if (values == null) {
return null;
}
int count = values.length;
String[] encodedValues = new String[count];
for (int i = 0; i < count; i++) {
encodedValues[i] = cleanXSS(values[i]);
}
return encodedValues;
}
//省略部分
private String cleanXSS(String value) {
//You'll need to remove the spaces from the html entities below
value = value.replaceAll("<", "& lt;").replaceAll(">", "& gt;");
value = value.replaceAll("\\(", "& #40;").replaceAll("\\)", "& #41;");
value = value.replaceAll("'", "& #39;");
value = value.replaceAll("eval\\((.*)\\)", "");
value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");
value = value.replaceAll("script", "");
return value;
}
}
複製代碼
本身只是過了一遍,要想更深刻的瞭解,還需好好努力啊,只是本身的筆記。