shiro-用户认证
#application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/shiro?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: 12345
type: com.alibaba.druid.pool.DruidDataSource
druid:
initial-size: 5 #初始连接数
max-active: 10 #最大活动连接
max-wait: 60000 #从池中取连接(没有闲置连接)的最大等待时间,-1表示无限等待
min-idle: 5 #最小闲置数,小于min-idle连接池会主动创建新的连接
time-between-eviction-runs-millis: 60000 #清理线程启动的间隔时间,当线程池中没有可用的连接启动清理线程
min-evictable-idle-time-millis: 300000 #清理线程保持闲置最小时间
validation-query: SELECT 1 #用于校验连接
test-on-borrow: false #请求连接时是否校验,比较消耗性能,一般设置false
test-on-return: false #归还连接时是否校验,比较消耗性能,一般设置false
test-while-idle: true #清理线程通过validation-query来校验连接是否正常,如果不正常将从连接池中移除
pool-prepared-statements: true #存储相同逻辑的sql到连接池的缓存中
# filters: stat,wall #监控统计web的statement(sql),以及防sql注入的wall
# 关闭如上配置,可以采用自定义的filter
filter:
stat:
enabled: true #状态监控-->stat
db-type: mysql
log-slow-sql: true #记录超过指定时间的sql到日志中
slow-sql-millis: 2000
wall:
enabled: true #防火墙-->wall
db-type: mysql
config:
delete-allow: false #禁止删除
drop-table-allow: false #禁止删除表
web-stat-filter:
enabled: true #开启监控uri,默认false
url-pattern: /* #添加过滤规则
exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid" #忽略过滤
stat-view-servlet:
enabled: true #开启视图管理界面,默认false
url-pattern: /druid/* #视图管理界面uri
login-username: druid #账号
login-password: 12345 #密码
# allow: 127.0.0.1 白名单
# deny: 192.168.1.130黑名单
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启log日志
lazy-loading-enabled: true
aggressive-lazy-loading: false
lazy-load-trigger-methods: ""
global-config:
db-config:
logic-delete-field: deleted #逻辑删除
logic-delete-value: 1 #已删除的值, 默认1
logic-not-delete-value: 0 #未删除的, 默认0
#mybaits-plus generator
@Test
public void generator() {
//主入口函数的路径
System.out.println(System.getProperty("user.dir"));
//代码生成器
AutoGenerator autoGenerator = new AutoGenerator();
//全局配置 调用generator.config下的
GlobalConfig gc = new GlobalConfig();
//获取当前项目的路径
String path = System.getProperty("user.dir");
//设置是否开启AR
gc.setAuthor("chz")
//文件输出路径
.setOutputDir(path + "/src/main/java")
//是否覆盖文件
.setFileOverride(true)
//设置主键自增策略
.setIdType(IdType.AUTO)
//是否开启resultMap,默认false
.setBaseResultMap(true)
//是否开启sql片段,默认false
.setBaseColumnList(true);
//数据源配置
DataSourceConfig dataSourceConfig = new DataSourceConfig();
dataSourceConfig.setDbType(DbType.MYSQL)
.setDriverName("com.mysql.cj.jdbc.Driver")
.setUrl("jdbc:mysql://localhost:3306/shiro?userSSL=false&serverTimezone=Asia/Shanghai")
.setUsername("root")
.setPassword("12345");
//策略配置
StrategyConfig strategyConfig = new StrategyConfig();
//是否开启大写命名,默认不开启
strategyConfig.setCapitalMode(false)
//数据库表映射到实体类命名策略
.setNaming(NamingStrategy.underline_to_camel)
//设置想要生成的表
.setInclude("t_perm","t_user","t_role")
//生成的dao,service,entity不再带tbl_前缀
.setTablePrefix("t_");
//包配置
PackageConfig packageConfig = new PackageConfig();
//setParent设置统一的包路径
packageConfig.setParent("com.chz")
//设置包的路径
.setMapper("mapper")
.setService("service")
.setController("controller")
.setEntity("entity")
.setXml("mapper");
//整合配置
autoGenerator.setPackageInfo(packageConfig)
.setDataSource(dataSourceConfig)
.setGlobalConfig(gc)
.setStrategy(strategyConfig);
//执行
autoGenerator.execute();
}
#pojo
//用户
@Data
@TableName("t_user")
public class User implements Serializable {
private static final long serialVersionUID=1L;
@TableId(value = "u_id", type = IdType.AUTO)
private Integer uId;
private String name;
private String password;
@TableField(exist = false)
private List<Role> roles;
}
//角色
@Data
@TableName("t_role")
public class Role implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "r_id", type = IdType.AUTO)
private Integer rId;
private String role;
@TableField(exist = false)
private List<Perm> perms;
}
//权限
@Data
@TableName("t_perm")
public class Perm implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "p_id", type = IdType.AUTO)
private Integer pId;
private String perm;
}
#mapper
在主启动类上使用了@MapperScan, 无需再使用@Mapper
//user
public interface UserMapper extends BaseMapper<User> {
@Select("SELECT * FROM t_user WHERE name =#{username}")
@Results({
@Result(property = "uId",column = "u_id",id = true),
@Result(property = "roles",column = "u_id",
many = @Many(select = "com.chz.mapper.RoleMapper.queryRole",fetchType = FetchType.LAZY))
})
User queryUser(@Param("username") String username);
}
//role
public interface RoleMapper extends BaseMapper<Role> {
@Select("SELECT * FROM t_role WHERE r_id IN (SELECT r_id FROM t_u_r WHERE u_id = #{u_id})")
@Results({
@Result(property = "rId", column = "r_id"),
@Result(property = "perms",column = "r_id",
many =@Many(select = "com.chz.mapper.PermMapper.queryPerms",fetchType = FetchType.LAZY))
})
Role queryRole(@Param("u_id") Integer uid);
}
//perm
public interface PermMapper extends BaseMapper<Perm> {
@Select("SELECT * FROM t_perm WHERE p_id IN (SELECT p_id FROM t_r_p WHERE r_id = #{r_id})")
List<Perm> queryPerms(@Param("r_id") Integer rId);
}
#Mvc配置类
/**
* Mvc配置类
*/
@Configuration
public class MvcConf implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index").setViewName("index");
}
}
#shiro配置类
/**
* shiro配置类
* 三大核心 ShiroFilterFactoryBean: filter 过滤url,交给SecurityManager代理
* SecurityManager: 代理filter和Realm
* Realm: 连接数据库的桥梁,权限与用户认证
*/
@Configuration
public class ShiroConf {
/**
*
* @param manager 安全代理
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultSecurityManager manager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//关联SecurityManager
shiroFilterFactoryBean.setSecurityManager(manager);
//通过set的方式设置url会放行请求
//登入的url
shiroFilterFactoryBean.setLoginUrl("/login");
//登入成功的url(由于请求是通过ajax发送的,所以不会生效,这里只起请求放行作用)
shiroFilterFactoryBean.setSuccessUrl("/index");
//设置未授权跳转的url
//shiroFilterFactoryBean.setUnauthorizedUrl("/");
//设置过滤,必须使用LinkedHashMap,否则会出现资源加在一次就被拦截
LinkedHashMap<String, String> filterChain = new LinkedHashMap<>();
/*
注意 anon 必须放在authc之上
支持ant
常用过滤器:
1. anon (anonymous): 无需认证(登入)可以访问资源
2. authc (authority): 必须认证才能访问
3. user : 如果使用rememberMe的功能可以访问, 同时认证通过的也可以访问
4. perms (permissions): 该资源必须拥有对应的资源权限才能访问
5. roles: 该资源源必须拥有对应的角色才能访问
*/
//filter会拦截所有请求,包括静态资源的请求
// filterChain.put("/css/**", "anon");
// filterChain.put("/js/**", "anon");
// filterChain.put("/img/**", "anon");
filterChain.put("/assert/**","anon");
//放行webjars
filterChain.put("/webjars/**","anon");
//放行druid
filterChain.put("/druid/**", "anon");
//登出放行
filterChain.put("/logout", "anon");
filterChain.put("/register","anon");
filterChain.put("/", "anon");
//除上述url外都必须认证通过才能访问, 为通过认证自动访问setLoginUrl()设置的url
filterChain.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChain);
return shiroFilterFactoryBean;
}
/**
* 关联Realm
* @param userRealm
* @return
*/
@Bean
public DefaultWebSecurityManager securityManager(Realm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联realm
securityManager.setRealm(userRealm);
return securityManager;
}
/**
* 用户认证与权限认证
* @param matcher 指定Shiro加密方式
*/
@Bean
public Realm userRealm() {
CustomizeRealm realm = new CustomizeRealm();
//告诉Shiro使用MD5加密
realm.setCredentialsMatcher(MD5());
return realm;
}
/**
* MD5加密
*/
private HashedCredentialsMatcher MD5() {
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
//指定加密方式为MD5
matcher.setHashAlgorithmName("MD5");
//加密次数
matcher.setHashIterations(5);
matcher.setStoredCredentialsHexEncoded(true);
return matcher;
}
/**
* 开始shiro标签
*/
@Bean
public ShiroDialect dialect() {
return new ShiroDialect();
}
#自定义Realm
@Slf4j
public class CustomizeRealm extends AuthorizingRealm {
@Autowired
private IUserService userService;
/**
* 获取用户权限和角色
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
/**
* 登入认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
//获取用户名
String username = usernamePasswordToken.getUsername();
User user = userService.queryUser(username);
//判断数据库中是否有该用户
if (ObjectUtils.isEmpty(user)) {
//如果不存在抛出用户不存在异常
throw new UnknownAccountException();
}
//获取加密后的密码
String password = user.getPassword();
//盐值
ByteSource salt = ByteSource.Util.bytes(username);
//密码校验由shiro完成
//principal,hashedcredentials,salt,realName
SimpleAuthenticationInfo info =
//校验加密后的密码盐值是否匹配
new SimpleAuthenticationInfo(username,
password,
salt,
getName());
return info;
}
}
#加密工具类
/**
* MD5加密工具类
*/
public class MD5Utils {
//加密方式可以时SHA1或是MD5
private static final String ALGORITHM_NAME = "MD5";
private static final int HASH_ITERATIONS = 5;
private static final String SALT = "chz";
//加密方式,加密对象,盐值,加密次数
public static String encrypt(String password, String username) {
SimpleHash hash = new SimpleHash(ALGORITHM_NAME, password, username, HASH_ITERATIONS);
return hash.toHex();
}
public static String encrypt(String password) {
SimpleHash hash = new SimpleHash(ALGORITHM_NAME, password, SALT, HASH_ITERATIONS);
return hash.toHex();
}
}
#controller
@Slf4j
@Controller
public class UserController {
@Autowired
IUserService userService;
@GetMapping("/login")
public String toLogin() {
return "login";
}
@ResponseBody
@PostMapping("/register")
public String register(@RequestBody User user){
String password = MD5Utils.encrypt(user.getPassword(),user.getName());
user.setPassword(password);
userService.save(user);
return "注册成功";
}
@ResponseBody
@PostMapping(value = "/login")
public String login(@RequestBody User user) {
log.info("在执行用户认证时调用了数据库,原因不明");
//获取当前用户
Subject currentUser = SecurityUtils.getSubject();
UsernamePasswordToken token =
new UsernamePasswordToken(user.getName(), user.getPassword());
try {
currentUser.login(token);
//@ResponseBody返回的如果是一个字符串,直接写到客户端;如果是对象才会将对象转为json串
//如果登入成功,返回0通过ajax判断重定向
return "0";
//为了安全不应该具体的显示错误
} catch (UnknownAccountException e) {
System.out.println("用户名错误");
return "用户名错误或密码错误";
} catch (IncorrectCredentialsException e) {
System.out.println("密码错误");
return "用户名错误或密码错误";
}
}