I. 서론
SpringBoot + 시로 + MyBatis로이 완료되었습니다.
시로, 약간의 튜토리얼 파트너를 보였다 소송을 따라하기 전에, 나는 많은 구덩이를 찾을 만났다 ( 'இ 요리를 இ`)
순서를 조금 수정 성공적으로 달렸다. 당신은 우편 배달부에 의해 테스트 할 수 있습니다
작은 비비 ∠ (ᐛ "∠) _ 직접 소스 코드에 : HTTPS : //github.com/niaobulashi/spring-boot-learning/tree/master/spring-boot-20-shiro
두, 시로는 유효
아파치 시로는 강력하고 유연한 오픈 소스 보안 프레임 워크입니다. 당신은 깨끗하게 인증, 권한 부여, 세션 관리 및 엔터프라이즈 암호화를 처리 할 수 있습니다.
두, 시로 일을 할 수있다
- 사용자 인증
- 사용자가 특정 보안 역할이 할당 된 경우 1 : 같은 사용자 액세스 제어,. 2는 사용자가 작업을 완료 할 수있는 권한을 부여 여부를 확인하는 방법
- 세션 API 임의로 비 - 웹 또는 EJB 컨테이너의 환경에서 사용할 수 있습니다
- 응답 인증, 액세스 제어, 또는 이벤트 세션의 라이프 사이클
- 하나 이상의 사용자 데이터 소스 보안 데이터는 복합 사용자 "보기"(뷰)에 결합 될 수있다
- 싱글 사인온 (SSO) 기능을 지원합니다
- 로그온 할 필요없이 "기억하기"서비스 사용자 관련 정보에 대한 접근의 제공을 지원
도 시로 프레임은 다음과 같이 :
- 인증 (인증) : 사용자 식별, 일반적으로 사용자가 "로그인"라
- 승인 (허가) : 액세스 제어. 예를 들어, 사용자는 작업을 사용할 수있는 권한이있는 경우.
- 세션 관리 (세션 관리) : 심지어 비 EJB 또는 웹 응용 프로그램에서 사용자 별 세션 관리.
- 암호화 (암호화) 사용의 용이성을 위해, 상기 데이터 소스를 암호화하는 암호 알고리즘을 사용하는 동안.
주제, 보안 관리자 및 영역 : 개념적 수준에서, 시로 아키텍처는 세 가지 주요 아이디어로 구성되어 있습니다. 다음 그림은 이러한 구성 요소는 우리가 차례로 설명한다, 상호 작용하는 방법을 보여줍니다.
- 제목 : 현재 사용자, 주제는 한 사람이 될 수 있지만, 타사 서비스, 데몬 계정, 크론 작업 또는 기타 될 수있다 - 현재의 모든 이벤트 및 소프트웨어 상호 작용을.
- 보안 관리자는 : 내부 안전 구성 요소가 함께 보안 우산을 구성하여 보안 관리자는 시로 코어 아키텍처, 모든 주제를 관리 할 수 있습니다.
- 영역 : 정보를 확인 할 수있는 권한을 사용하여, 우리는 실현을 소유하고 있습니다. 영역은 특정 보안 DAO에 본질적 : 데이터 패키지에 연결된 소스의 세부 시로에 필요한 관련 데이터를 얻을 수 있습니다. 시로를 구성 할 때 인증 (인증) 및 / 또는 인증 (인증)를 구현하는 적어도 하나 개의 영역을 지정해야합니다.
셋째, 코드 구현
1, 메이븐 의존 추가
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
2, 구성 파일
application.yml
# 服务器端口
server:
port: 8081
# 配置Spring相关信息
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
username: root
password: root
# 配置Mybatis
mybatis:
type-aliases-package: com.niaobulashi.model
mapper-locations: classpath:mapper/*.xml
configuration:
# 开启驼峰命名转换
map-underscore-to-camel-case: true
# 打印SQL日志
logging:
level:
com.niaobulashi.mapper: DEBUG
매퍼를 추가하는 방법을 스캔 시작, 나는 보통 별도의 문이 각 매퍼에 스캔이 필요합니다 별도의 언급이 방법은 위의 시작
@SpringBootApplication
@MapperScan("com.niaobulashi.mapper")
public class ShiroApplication {
public static void main(String[] args) {
SpringApplication.run(ShiroApplication.class, args);
}
}
3, 간단한 테이블 디자인
사용자 테이블, 테이블 역할, 권한 테이블, 테이블 사용자 역할, 역할 권한 테이블 : 5 개 개의 테이블에 지나지 않습니다.
아래이 사진을 봐, 꽤 명확하게 할 수 있습니다.
특히 나는 그들이 너무 많은 공간을 차지 넣어하지 않습니다. . 직접 연결된 링크 : HTTPS : //github.com/niaobulashi/spring-boot-learning/blob/master/spring-boot-20-shiro/db/test.sql
4, 엔티티 클래스
User.java
@Data
public class User implements Serializable {
private static final long serialVersionUID = -6056125703075132981L;
private Integer id;
private String account;
private String password;
private String username;
}
Role.java
@Data
public class Role implements Serializable {
private static final long serialVersionUID = -1767327914553823741L;
private Integer id;
private String role;
private String desc;
}
5 매퍼 층
여기에서 우리는 요약 : 데이터베이스 작업에 관한 간단한 사용자 로그인 권한 시로 컨트롤이 주로 사입니다
- 사용자 로그인 이름은 쿼리 사용자 정보를
- 에 따르면, 사용자 쿼리 역할 정보
- 根据角色查询权限信息
UserMapper.java/UserMapper.xml
public interface UserMapper {
/**
* 根据账户查询用户信息
* @param account
* @return
*/
User findByAccount(@Param("account") String account);
}
<!--用户表结果集-->
<sql id="base_column_list">
id, account, password, username
</sql>
<!--根据账户查询用户信息-->
<select id="findByAccount" parameterType="Map" resultType="com.niaobulashi.model.User">
select
<include refid="base_column_list"/>
from user
where account = #{account}
</select>
RoleMapper.java/RoleMapper.xml
public interface RoleMapper {
/**
* 根据userId查询角色信息
* @param userId
* @return
*/
List<Role> findRoleByUserId(@Param("userId") Integer userId);
}
<!--角色表字段结果集-->
<sql id="base_cloum_list">
id, role, desc
</sql>
<!--根据userId查询角色信息-->
<select id="findRoleByUserId" parameterType="Integer" resultType="com.niaobulashi.model.Role">
select r.id, r.role
from role r
left join user_role ur on ur.role_id = r.id
left join user u on u.id = ur.user_id
where 1=1
and u.user_id = #{userId}
</select>
PermissionMapper.java/PermissionMapper.xml
public interface PermissionMapper {
/**
* 根据角色id查询权限
* @param roleIds
* @return
*/
List<String> findByRoleId(@Param("roleIds") List<Integer> roleIds);
}
<!--权限查询结果集-->
<sql id="base_column_list">
id, permission, desc
</sql>
<!--根据角色id查询权限-->
<select id="findByRoleId" parameterType="List" resultType="String">
select permission
from permission, role_permission rp
where rp.permission_id = permission.id and rp.role_id in
<foreach collection="roleIds" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
6、Service层
没有其他逻辑,只有继承。
注意:
不过需要注意的一点是,我在Service层中,使用的注解@Service:启动时会自动注册到Spring容器中。
否则启动时,拦截器配置初始化时,会找不到Service。。。这点有点坑。
UserService.java/UserServiceImpl.java
public interface UserService {
/**
* 根据账户查询用户信息
* @param account
* @return
*/
User findByAccount(String account);
}
@Service("userService")
public class UserServiceImpl implements UserService {
@Resource
private UserMapper userMapper;
/**
* 根据账户查询用户信息
* @param account
* @return
*/
@Override
public User findByAccount(String account) {
return userMapper.findByAccount(account);
}
}
RoleService.java/RoleServiceImpl.java
public interface RoleService {
/**
* 根据userId查询角色信息
* @param id
* @return
*/
List<Role> findRoleByUserId(Integer id);
}
@Service("roleService")
public class RoleServiceImpl implements RoleService {
@Resource
private RoleMapper roleMapper;
/**
* 根据userId查询角色信息
* @param id
* @return
*/
@Override
public List<Role> findRoleByUserId(Integer id) {
return roleMapper.findRoleByUserId(id);
}
}
PermissionService.java/PermissionServiceImpl.java
public interface PermissionService {
/**
* 根据角色id查询权限
* @param roleIds
* @return
*/
List<String> findByRoleId(@Param("roleIds") List<Integer> roleIds);
}
@Service("permissionService")
public class PermissionServiceImpl implements PermissionService {
@Resource
private PermissionMapper permissionMapper;
/**
* 根据角色id查询权限
* @param roleIds
* @return
*/
@Override
public List<String> findByRoleId(List<Integer> roleIds) {
return permissionMapper.findByRoleId(roleIds);
}
}
7、系统统一返回状态枚举和包装方法
状态字段枚举
StatusEnmus.java
public enum StatusEnums {
SUCCESS(200, "操作成功"),
SYSTEM_ERROR(500, "系统错误"),
ACCOUNT_UNKNOWN(500, "账户不存在"),
ACCOUNT_IS_DISABLED(13, "账号被禁用"),
INCORRECT_CREDENTIALS(500,"用户名或密码错误"),
PARAM_ERROR(400, "参数错误"),
PARAM_REPEAT(400, "参数已存在"),
PERMISSION_ERROR(403, "没有操作权限"),
NOT_LOGIN_IN(15, "账号未登录"),
OTHER(-100, "其他错误");
@Getter
@Setter
private int code;
@Getter
@Setter
private String message;
StatusEnums(int code, String message) {
this.code = code;
this.message = message;
}
}
响应包装方法
ResponseCode.java
@Data
@AllArgsConstructor
public class ResponseCode<T> implements Serializable {
private Integer code;
private String message;
private Object data;
private ResponseCode(StatusEnums responseCode) {
this.code = responseCode.getCode();
this.message = responseCode.getMessage();
}
private ResponseCode(StatusEnums responseCode, T data) {
this.code = responseCode.getCode();
this.message = responseCode.getMessage();
this.data = data;
}
private ResponseCode(Integer code, String message) {
this.code = code;
this.message = message;
}
/**
* 返回成功信息
* @param data 信息内容
* @param <T>
* @return
*/
public static<T> ResponseCode success(T data) {
return new ResponseCode<>(StatusEnums.SUCCESS, data);
}
/**
* 返回成功信息
* @return
*/
public static ResponseCode success() {
return new ResponseCode(StatusEnums.SUCCESS);
}
/**
* 返回错误信息
* @param statusEnums 响应码
* @return
*/
public static ResponseCode error(StatusEnums statusEnums) {
return new ResponseCode(statusEnums);
}
}
8、Shiro配置
ShiroConfig.java
@Configuration
public class ShiroConfig {
/**
* 路径过滤规则
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setSuccessUrl("/");
// 拦截器
Map<String, String> map = new LinkedHashMap<>();
// 配置不会被拦截的链接 顺序判断
map.put("/login", "anon");
// 过滤链定义,从上向下顺序执行,一般将/**放在最为下边
// 进行身份认证后才能访问
// authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
map.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
/**
* 自定义身份认证Realm(包含用户名密码校验,权限校验等)
* @return
*/
@Bean
public AuthRealm authRealm() {
return new AuthRealm();
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(authRealm());
return securityManager;
}
/**
* 开启Shiro注解模式,可以在Controller中的方法上添加注解
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
扩展:权限拦截Filter的URL的一些说明
这里扩展一下权限拦截Filter的URL的一些说明
1、URL匹配规则
(1)“?”:匹配一个字符,如”/admin?”,将匹配“ /admin1”、“/admin2”,但不匹配“/admin”
(2)“”:匹配零个或多个字符串,如“/admin”,将匹配“ /admin”、“/admin123”,但不匹配“/admin/1”
(3)“”:匹配路径中的零个或多个路径,如“/admin/”,将匹配“/admin/a”、“/admin/a/b”
2、shiro过滤器
Filter | 解释 |
---|---|
anon | 无参,开放权限,可以理解为匿名用户或游客 |
authc | 无参,需要认证 |
logout | 无参,注销,执行后会直接跳转到shiroFilterFactoryBean.setLoginUrl(); 设置的 url |
authcBasic | 无参,表示 httpBasic 认证 |
user | 无参,表示必须存在用户,当登入操作时不做检查 |
ssl | 无参,表示安全的URL请求,协议为 https |
perms[user] | 参数可写多个,表示需要某个或某些权限才能通过,多个参数时写 perms["user, admin"],当有多个参数时必须每个参数都通过才算通过 |
roles[admin] | 参数可写多个,表示是某个或某些角色才能通过,多个参数时写 roles["admin,user"],当有多个参数时必须每个参数都通过才算通过 |
rest[user] | 根据请求的方法,相当于 perms[user:method],其中 method 为 post,get,delete 等 |
port[8081] | 当请求的URL端口不是8081时,跳转到schemal://serverName:8081?queryString 其中 schmal 是协议 http 或 https 等等,serverName 是你访问的 Host,8081 是 Port 端口,queryString 是你访问的 URL 里的 ? 后面的参数 |
常用的主要就是 anon,authc,user,roles,perms 等
참고 : 아논, authc가 authcBasic는 사용자 인증 필터 파마 포트, 나머지 역할 제 1 세트 인 , SSL ,이 처음 로그인 인증 동작 (즉, 제 완료해야 2 필터 그룹 승인 필터를 통해 승인 당신이 도착하지 않은 경우 같은 액세스 권한 필요한 역할 URL로 승인 된 장치 (의 두 번째 세트를 취할) 찾아 가서 인증 인증을 완료하는 데, 그것은 직접 이동합니다 shiroFilterFactoryBean.setLoginUrl();
) URL 설정.
9, 사용자 정의 영역
홈페이지 후계자 AuthorizingRealm
방법은 우선, doGetAuthorizationInfo
,doGetAuthenticationInfo
승인 :doGetAuthorizationInfo
인증 :doGetAuthenticationInfo
AuthRealm.java
public class AuthRealm extends AuthorizingRealm {
@Resource
private UserService userService;
@Resource
private RoleService roleService;
@Resource
private PermissionService permissionService;
/**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
User user = (User) principalCollection.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// 根据用户Id查询角色信息
List<Role> roleList = roleService.findRoleByUserId(user.getId());
Set<String> roleSet = new HashSet<>();
List<Integer> roleIds = new ArrayList<>();
for (Role role : roleList) {
roleSet.add(role.getRole());
roleIds.add(role.getId());
}
// 放入角色信息
authorizationInfo.setRoles(roleSet);
// 放入权限信息
List<String> permissionList = permissionService.findByRoleId(roleIds);
authorizationInfo.setStringPermissions(new HashSet<>(permissionList));
return authorizationInfo;
}
/**
* 认证
* @param authToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authToken;
// 根据用户名查询用户信息
User user = userService.findByAccount(token.getUsername());
if (user == null) {
return null;
}
return new SimpleAuthenticationInfo(user, user.getPassword(), getName());
}
}
10 Contrller 층
@RestController
public class LoginController {
/**
* 登录操作
* @param user
* @return
*/
@RequestMapping(value = "/login", method = RequestMethod.POST)
public ResponseCode login(@RequestBody User user) {
Subject userSubject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(user.getAccount(), user.getPassword());
try {
// 登录验证
userSubject.login(token);
return ResponseCode.success();
} catch (UnknownAccountException e) {
return ResponseCode.error(StatusEnums.ACCOUNT_UNKNOWN);
} catch (DisabledAccountException e) {
return ResponseCode.error(StatusEnums.ACCOUNT_IS_DISABLED);
} catch (IncorrectCredentialsException e) {
return ResponseCode.error(StatusEnums.INCORRECT_CREDENTIALS);
} catch (Throwable e) {
e.printStackTrace();
return ResponseCode.error(StatusEnums.SYSTEM_ERROR);
}
}
@GetMapping("/login")
public ResponseCode login() {
return ResponseCode.error(StatusEnums.NOT_LOGIN_IN);
}
@GetMapping("/auth")
public String auth() {
return "已成功登录";
}
@GetMapping("/role")
@RequiresRoles("vip")
public String role() {
return "测试Vip角色";
}
@GetMapping("/permission")
@RequiresPermissions(value = {"add", "update"}, logical = Logical.AND)
public String permission() {
return "测试Add和Update权限";
}
/**
* 登出
* @return
*/
@GetMapping("/logout")
public ResponseCode logout() {
getSubject().logout();
return ResponseCode.success();
}
}
넷째, 테스트
1. 로그 : HTTP : // localhost를 : 8081 / 로그인
{
"account":"123",
"password":"232"
}
2, 다른 라인에 직접 전송 요청 URL을 얻을 수 있습니다.
인터페이스 테스트가 안전하게 먹을 수 있으며, 통과했다.
추천 도서 :
장 Kaitao 오래된 "시로 나를 따르라"https://jinnianshilongnian.iteye.com/blog/2018936