SprnigBoot整合Shiro
SpringBoot中引用Shiro进行基本的登陆验证,orm框架使用Spring Data JPA 默认实现是Hibernate。
pom中引入Shiro依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring-boot-starter -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.8.0</version>
</dependency>
appliaction.yml
server:
port: 8090
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/remind?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true&createDatabaseIfNotExist=true
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123
jpa:
hibernate:
ddl-auto: update
database-platform: org.hibernate.dialect.MySQL55Dialect
show-sql: false
编写存储用户的实体类
- BaseEntity.java 将一些entitiy中通用的字段放到BaseEntity中
@Data
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor
@MappedSuperclass
public abstract class BaseEntity implements Serializable {
@Id
@GeneratedValue(generator = "uuidGen")
@GenericGenerator(name = "uuidGen", strategy = "uuid")
private String uuid;
private Date createTime;
private Date updateTime;
}
- UserEntity.java
@Table(name = "t_user")
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@SuperBuilder
@EqualsAndHashCode(callSuper = true)
public class UserEntity extends BaseEntity {
@Column(unique = true,nullable = false) //设置唯一约束
private String name;
@Column(nullable = false) //设置不能为null
private String password;
private String email;
@Column(columnDefinition = "varchar(255) default '北京' ") //设置默认值
private String city;
}
dao层
@Repository
public interface UserRepository extends JpaRepository<UserEntity, String> {
UserEntity findUserEntityByName(String name); //指定命名方法名,可以直接解析查询name字段
}
service层
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserRepository userRepo;
@Override
public UserEntity saveUser(UserEntity user) {
return null;
}
@Override
public UserEntity findUserByName(String name) {
UserEntity user = userRepo.findUserEntityByName(name);
if(null == user) throw new RuntimeException("用户不存在");
return user;
}
@Override
public UserEntity login(String name, String password) {
UsernamePasswordToken token = new UsernamePasswordToken(name, password);
Subject subject = SecurityUtils.getSubject();
subject.login(token);
return null;
}
}
自定义AuthorizingRealm
@Component
public class MyRealm extends AuthorizingRealm {
@Autowired
UserService userService;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
//验证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//1获取用户身份信息
String name = authenticationToken.getPrincipal().toString();
//2数据库中查找用户
UserEntity user = userService.findUserByName(name);
if(null != user) {
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashAlgorithmName("MD5"); //设置为MD5加密算法
matcher.setHashIterations(3); //设置hash迭代次数,多次迭代安全性提高
return new SimpleAuthenticationInfo(
authenticationToken.getPrincipal(),
user.getPassword(),
ByteSource.Util.bytes("sallai"),
name
);
}
return null;
}
}
Shiro配置类
@Configuration
public class ShiroConfig {
@Autowired
MyRealm realm;
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashIterations(3);
matcher.setHashAlgorithmName("MD5");
realm.setCredentialsMatcher(matcher);
securityManager.setRealm(realm);
return securityManager;
}
@Bean(name = "shiroFilterFactoryBean")
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
bean.setLoginUrl("/api/user/loginPage"); //设置登陆页路径
bean.setSuccessUrl("/index");
Map<String, String> map = new LinkedHashMap<>();
map.put("/api/user/login", "anon"); //放行路径
map.put("/**", "authc"); //拦截所有放在最后
bean.setFilterChainDefinitionMap(map);
return bean;
}
}
返回R类
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class R {
private int code;
private String msg;
private Object data;
private final static Integer SUCCESS_CODE = 200;
private final static Integer FAIL_CODE = 401;
private final static String SUCCESS_TIP_DEFAULT = "操作成功";
private final static String FAIL_TIP_DEFAULT = "操作失败!";
public static R success(){
R r = new R();
r.setCode(SUCCESS_CODE);
r.setMsg(SUCCESS_TIP_DEFAULT);
return r;
}
public static R fail(){
R r = new R();
r.setCode(FAIL_CODE);
r.setMsg(FAIL_TIP_DEFAULT);
return r;
}
public R setMsg(String msg) {
this.msg = msg;
return this;
}
@Override
public String toString() {
return JSON.toJSONString(this);
}
}
编写Controller测试
@RestController
@RequestMapping("/api/user")
public class UserController {
@Autowired
UserService userService;
@GetMapping("login")
public R userLogin(String name,String password) {
userService.login(name, password);
return R.success().setMsg("登陆成功!");
}
@GetMapping("loginPage")
public R userLoginPage() {
return R.success().setMsg("please login");
}
}
全局异常处理
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(value = {RuntimeException.class})
public R handleRuntimeException(RuntimeException runtimeException) {
log.info("捕获全局异常->"+runtimeException.getLocalizedMessage().toString());
return R.fail().setMsg(runtimeException.getLocalizedMessage().toString());
}
}
编写测试,生成对应加盐,三次MD5密码
@Test
public void genMd5Password(){
Md5Hash hash = new Md5Hash("123", "sallai", 3); //明文密码 123,slat盐值 sallai,hash迭代次数,与代码中写的次数要一直,不然检验的时候对应不上。
log.info(hash.toHex());
//将打印的结果,添加的数据中的password字段中,name自己写一个。我这里sallai用户名
}
测试
正确测试
异常测试
这里可以在subject.login()中,trycatch一下,捕获对应的用户名不存在异常和密码验证失败异常,手动抛出运行时异常信息,填写对应的异常提示消息,然后这里就会得到对应的异常提示信息。