原始碼地址
后端
https://github.com/jonssonyan/authority
前端
https://github.com/jonssonyan/authority-ui
系統界面


專案結構
后端

前端

資料庫

核心代碼解釋
ShiroConfig
Shiro核心配置類,里面有很多物件,值得好好看看,這里不一一舉例,以下是該類中將用戶密碼使用加密存盤的配置
/*
* 憑證匹配器 由于我們的密碼校驗交給Shiro的SimpleAuthenticationInfo進行處理了
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("MD5");// 散列演算法:這里使用MD5演算法;
hashedCredentialsMatcher.setHashIterations(1024);// 散列的次數,比如散列兩次,相當于MD5(MD5(""));
return hashedCredentialsMatcher;
}
UserRealm
用戶登陸時的認證和授權
@Slf4j
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Autowired
private RolePermissionService rolePermissionService;
@Autowired
private PermissionService permissionService;
@Autowired
private RoleService roleService;
@Autowired
private MenuListService menuListService;
@Autowired
private RoleMenuListService roleMenuListService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 執行授權
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// 設定角色
List<Role> roles = roleService.selectRoles(SecurityUtil.getCurrentUser().getRoleId(), true);
authorizationInfo.addRoles(roles.stream().map(Role::getName).collect(Collectors.toList()));
List<RolePermission> rolePermissions = rolePermissionService.lambdaQuery().eq(RolePermission::getRoleId, SecurityUtil.getCurrentUser().getRoleId()).list();
if (CollectionUtil.isNotEmpty(rolePermissions)) {
Set<Permission> set = new HashSet<>();
for (RolePermission rolePermission : rolePermissions) {
List<Permission> permissions = permissionService.lambdaQuery().eq(Permission::getId, rolePermission.getPermissionId()).list();
set.addAll(permissions);
}
// 設定權限
authorizationInfo.addStringPermissions(set.stream().map(Permission::getName).collect(Collectors.toList()));
}
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) {
if (authenticationToken.getPrincipal() == null) {
return null;
}
// 執行認證
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
User user = userService.selectByUsername(usernamePasswordToken.getUsername());
// 判斷用戶
if (user == null) {
throw new UnknownAccountException("用戶不存在!");
}
if (user.getState() == 0) {
throw new DisabledAccountException("賬號已被禁用!");
}
// 認證成功之后設定角色關聯的選單
List<RoleMenuList> roleMenuLists = roleMenuListService.lambdaQuery().in(RoleMenuList::getRoleId, user.getRoleId()).list();
if (CollectionUtil.isNotEmpty(roleMenuLists)) {
List<Long> collect = roleMenuLists.stream().map(RoleMenuList::getMenuListId).collect(Collectors.toList());
List<MenuList> menuLists = menuListService.lambdaQuery().in(CollectionUtil.isNotEmpty(collect), MenuList::getId, collect).list();
// 認證成功之后設定角色關聯的選單
user.setMenuLists(CollectionUtil.isNotEmpty(collect) ? menuLists : null);
}
return new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(SystemConstants.JWT_SECRET_KEY), getName());
}
}
JWTRealm
請求介面時需要攜帶token,該類用于對token的認證和授權
@Slf4j
public class JWTRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Autowired
private RolePermissionService rolePermissionService;
@Autowired
private PermissionService permissionService;
@Autowired
private RoleService roleService;
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JwtToken;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 執行授權
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// 設定角色
List<Role> roles = roleService.selectRoles(SecurityUtil.getCurrentUser().getRoleId(), true);
authorizationInfo.addRoles(roles.stream().map(Role::getName).collect(Collectors.toList()));
List<RolePermission> rolePermissions = rolePermissionService.lambdaQuery().eq(RolePermission::getRoleId, SecurityUtil.getCurrentUser().getRoleId()).list();
Set<Permission> set = new HashSet<>();
for (RolePermission rolePermission : rolePermissions) {
List<Permission> permissions = permissionService.lambdaQuery().eq(Permission::getId, rolePermission.getPermissionId()).list();
set.addAll(permissions);
}
// 設定權限
authorizationInfo.addStringPermissions(set.stream().map(Permission::getName).collect(Collectors.toList()));
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String token = (String) authenticationToken.getCredentials();
// 解密獲得username,用于和資料庫進行對比
String username = JwtUtils.getUsernameByToken(token);
if (StrUtil.isBlank(username)) {
throw new AuthenticationException("token認證失敗!");
}
User user = userService.selectByUsername(username);
// 判斷用戶
if (user == null) {
throw new AuthenticationException("用戶不存在!");
}
if (user.getState() == 0) {
throw new AuthenticationException("賬號已被禁用!");
}
return new SimpleAuthenticationInfo(user, token, getName());
}
}
NoSessionFilter
該類用于過濾請求,獲取請求中的token,并對其認證
@Slf4j
public class NoSessionFilter extends BasicHttpAuthenticationFilter {
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
HttpServletRequest servletRequest = (HttpServletRequest) request;
// 1.從Cookie獲取token
String token = getTokenFromCookie(servletRequest);
if (StrUtil.isBlank(token)) {
// 2.從headers中獲取
token = servletRequest.getHeader(SystemConstants.TOKEN_HEADER);
}
if (StrUtil.isBlank(token)) {
// 3.從請求引數獲取
token = request.getParameter(SystemConstants.TOKEN_HEADER);
}
if (StrUtil.isBlank(token)) {
return false;
}
// 驗證token
token = token.replace(SystemConstants.TOKEN_PREFIX, "");
JwtToken jwtToken = new JwtToken(token);
// 提交給realm進行登入,如果錯誤他會拋出例外并被捕獲
// todo https://www.cnblogs.com/red-star/p/12121941.html https://blog.csdn.net/qq_43721032/article/details/110188342
try {
SecurityUtils.getSubject().login(jwtToken);
} catch (Exception e) {
return false;
}
// 如果沒有拋出例外則代表登入成功,回傳true
return true;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
PrintWriter printWriter = response.getWriter();
response.setCharacterEncoding("utf-8");
printWriter.write("403");
printWriter.flush();
printWriter.close();
return false;
}
private String getTokenFromCookie(HttpServletRequest request) {
String token = null;
Cookie[] cookies = request.getCookies();
int len = null == cookies ? 0 : cookies.length;
if (len > 0) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals(SystemConstants.TOKEN_HEADER)) {
token = cookie.getValue();
break;
}
}
}
return token;
}
/**
* 對跨域提供支持
*/
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
// 跨域時會首先發送一個option請求,這里我們給option請求直接回傳正常狀態
if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
httpServletResponse.setStatus(HttpStatus.OK.value());
return false;
}
return super.preHandle(request, response);
}
}
UserController
使用Shiro的注解對介面進行角色和權限的控制
/**
* 分頁查詢用戶
* 需要管理員權限
*
* @param userVO
* @return
*/
@PostMapping("/selectPage")
@RequiresRoles({"admin"})
@RequiresPermissions({"user:select"})
public Result<Object> selectPage(@RequestBody UserVO userVO) {
return Result.success(userService.selectPage(userVO));
}
DefaultController
登陸時創建token回傳給用戶,同時將用戶名和加密后的密碼與資料庫中比對
/**
* 登錄
*
* @param user 登錄的用戶物件
* @return
*/
@PostMapping("/login")
public Result<Object> findByUsernameAndPassword(@RequestBody User user) {
if (!SecurityUtils.getSubject().isAuthenticated()) {
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(user.getUsername(), user.getPassword(), true);
try {
// shiro驗證用戶名密碼
SecurityUtils.getSubject().login(usernamePasswordToken);
// 生成token
String token = JwtUtils.createToken(user.getUsername(), false);
// 將用戶戶名和token回傳
HashMap<String, String> map = new HashMap<>();
map.put("username", user.getUsername());
map.put("Authorization", token);
map.put("role_id", SecurityUtil.getCurrentUser().getRoleId().toString());
return Result.success(map);
} catch (IncorrectCredentialsException e) {
return Result.fail("登錄密碼錯誤");
} catch (ExcessiveAttemptsException e) {
return Result.fail("登錄失敗次數過多");
} catch (LockedAccountException e) {
return Result.fail("帳號已被鎖定");
} catch (DisabledAccountException e) {
return Result.fail("帳號已被禁用");
} catch (ExpiredCredentialsException e) {
return Result.fail("帳號已過期");
} catch (UnknownAccountException e) {
return Result.fail("帳號不存在");
} catch (UnauthorizedException e) {
return Result.fail("您沒有得到相應的授權");
} catch (Exception e) {
return Result.fail("出錯了!!!");
}
}
return Result.fail("你已經登錄了");
}
總結
該系統使用Shrio框架對介面實作角色和權限的控制,角色權限和授權可通過用戶互動界面動態實作,用戶登錄使用jwt單點登錄,并將用戶密碼使用鹽加密存盤,從而保證了介面和用戶密碼的安全性,注意,不可將admin賬戶設定成禁用狀態否則沒辦法登錄(除了改資料庫),應為狀態一旦禁用只可以管理員進行解禁,每一個大型系統都會有權限的管理,權限管理在實際作業中經常遇到,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/263765.html
標籤:其他
上一篇:幾個實用的 Bat 腳本命令
