依赖版本:
图上有nacos的依赖nacos的服务注册和服务发现可以不需要,链接数据库连接池必须有, // thymeleaf依赖是为了展示页面
implementation(“org.springframework.boot:spring-boot-starter-web”)
// shiro
implementation(“org.apache.shiro:shiro-web:1.8.0”)
implementation(“org.apache.shiro:shiro-spring:1.8.0”)
implementation(“org.apache.shiro:shiro-core:1.8.0”)
//数据库
implementation("org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.4")
//HikariCP连接池
implementation("com.zaxxer:HikariCP:3.3.1")
implementation("mysql:mysql-connector-java:8.0.11")
// thymeleaf-extras-shiro
implementation("com.github.theborakompanioni:thymeleaf-extras-shiro:2.0.0")
implementation("org.springframework.boot:spring-boot-starter-thymeleaf:2.5.4")
项目目录
下面的/s/addUserView的这些路径看自己项目写(页面不能成功拦截先去看自己的路径对不对)
/**
-
Shiro的配置类
-
@author lenovo
/
@Configuration
class ShiroConfig {
/*-
创建ShiroFilterFactoryBean
*/
@Bean
fun getShiroFilterFactoryBean(@Qualifier(“securityManager”) securityManager: DefaultWebSecurityManager?): ShiroFilterFactoryBean {
val shiroFilterFactoryBean = ShiroFilterFactoryBean()//设置安全管理器
shiroFilterFactoryBean.securityManager = securityManager
//添加Shiro内置过滤器
/**- Shiro内置过滤器,可以实现权限相关的拦截器
- 常用的过滤器:
- anon: 无需认证(登录)可以访问
- authc: 必须认证才可以访问
- user: 如果使用rememberMe的功能可以直接访问
- perms: 该资源必须得到资源权限才可以访问
- role: 该资源必须得到角色权限才可以访问
/
val filterMap: MutableMap<String, String> = LinkedHashMap()
// filterMap.put("/s/add", “authc”);
// filterMap.put("/s/update", “authc”);
//放行login.html页面
filterMap["/s/login"] = “anon”
filterMap["/s/addUserView"] = “anon”
filterMap["/s/addUser"] = “anon”
filterMap["/s/"] = “authc”
//授权过滤器
//注意:当前授权拦截后,shiro会自动跳转到未授权页面
filterMap["/s/add"] = “perms[user:add]”
filterMap["/s/update"] = “perms[user:update]”
// filterMap["/findAll"] = “perms[user:findAll]”
//filterMap["/s/*"] = “authc”//修改调整的登录页面
shiroFilterFactoryBean.loginUrl = “/s/toLogin”
//设置未授权提示页面
shiroFilterFactoryBean.unauthorizedUrl = “/s/noAuth”
shiroFilterFactoryBean.filterChainDefinitionMap = filterMap
return shiroFilterFactoryBean
}
/**
- 创建DefaultWebSecurityManager
*/
@Bean(name = [“securityManager”])
fun getDefaultWebSecurityManager(@Qualifier(“userRealm”) userRealm: UserRealm?): DefaultWebSecurityManager {
val securityManager = DefaultWebSecurityManager()
//关联realm
securityManager.setRealm(userRealm)
return securityManager
}
/**
- 创建Realm
*/
@Bean(name = [“userRealm”])
fun getRealm(): UserRealm? {
return UserRealm()
}
/**
- 配置ShiroDialect,用于thymeleaf和shiro标签配合使用
*/
@Bean
fun getShiroDialect(): ShiroDialect? {
return ShiroDialect()
}
/**
- 凭证匹配器
- (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
- )
- @return
*/
@Bean
fun hashedCredentialsMatcher(): HashedCredentialsMatcher? {
val hashedCredentialsMatcher = HashedCredentialsMatcher()
hashedCredentialsMatcher.hashAlgorithmName = “md5” //散列算法:这里使用MD5算法;
hashedCredentialsMatcher.hashIterations = 1024 //散列的次数,比如散列两次,相当于 md5(md5(""));
return hashedCredentialsMatcher
}//将自己的验证方式加入容器
@Bean
fun myShiroRealm(): UserRealm? {
val myShiroRealm = UserRealm()
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher())
return myShiroRealm
} -
}
Usercontroller:
@Controller
@RequestMapping("/user")
class UserController {
@Resource
lateinit var userService: UserService
/**
* 测试方法
*/
@RequestMapping("/addUserView")
fun hello(): String {
return "/addUser"
}
@RequestMapping("/add")
fun add(): String {
return "/user/add"
}
@RequestMapping("/update")
fun update(): String {
return "/user/update"
}
@RequestMapping("/toLogin")
fun toLogin(): String {
return "/login"
}
//通过controller返回html界面
@RequestMapping("/index")
fun indexJumpPage(): String {
return "/login"
}
// 未授权
@RequestMapping("/noAuth")
fun noAuth(): String {
return "/noAuth"
}
/**
* 全查
*/
@RequestMapping("/findAll")
@ResponseBody
fun findAll(id: String): List<User>? {
//把数据存入model
// print(userService.findAll(id).toString())
//返回test.html
val u: List<User> = userService.findAll(id)!!
return u
}
/**
* 登录逻辑处理
*/
@PostMapping("/login")
fun login(name: String, password: String, model: Model): String {
/**
* 使用Shiro编写认证操作
*/
//1.获取Subject
val subject = SecurityUtils.getSubject()
val u = userService.findByName(name)
// 两个参数,第一个是需要加密的字符串,第二个是盐
var hash = Md5Hash(password, name.toString() + u?.salt)
var hashpassword = hash.toString()
//2.封装用户数据
val token = UsernamePasswordToken(name, hashpassword)
//3.执行登录方法
return try {
// 没有异常登录成功
subject.login(token)
//登录成功
return "test"
} catch (e: UnknownAccountException) {
//e.printStackTrace();
//登录失败:用户名不存在
model.addAttribute("msg", "用户名不存在")
return "login"
} catch (e: IncorrectCredentialsException) {
//e.printStackTrace();
//登录失败:密码错误
print("密码错误")
model.addAttribute("msg", "密码错误")
return "login"
}
}
/**
* 注册加密(md5+salt(盐))
*/
@PostMapping("/addUser")
fun addUser(user: User):String {
// 生成一个随机4位数字符串(只包含大小写字母,数字)
val salt: String = RandomUtil.generateString(4)
user.salt= salt
user.id = UUID.randomUUID().toString()
user.perms = "user:add"
// 两个参数,第一个是需要加密的字符串,第二个是盐
val hash = Md5Hash(user.password, user.name.toString() + salt)
user.password =hash.toString()
userService.addUser(user)
return "login"
}
}
实体类
@Mapper
interface UserMapper{
@Select(
“SELECT id,NAME,PASSWORD,perms,salt FROM user where name = #{value}”
)
fun findByName(name: String?): User?
@Select("SELECT id,NAME,PASSWORD,perms FROM user where id = #{value}")
fun findById(id: String?): User?
@Select("select*from user where id = #{id}")
fun findAll(id:String?) :List<User>?
@Insert("insert into user(id,name,password,perms,salt) values(#{id},#{name},#{password},#{perms},#{salt})")
fun addUser(user : User)
}
RandomUtil类加密方式
class RandomUtil : Random {
constructor() : super() {}
constructor(seed: Long) : super(seed) {}
fun nextInt(n: Int, size: Int): IntArray {
var size = size
if (size > n) {
size = n
}
val set: MutableSet<*> = LinkedHashSet<Any?>()
for (i in 0 until size) {
while (true) {
val value: Int = nextInt(n)
if (!set.contains(value)) {
set.add(value as Nothing)
break
}
}
}
val array = IntArray(set.size)
val itr: Iterator<*> = set.iterator()
for (i in array.indices) {
array[i] = (itr.next() as Int).toInt()
}
return array
}
fun randomize(array: CharArray) {
var length = array.size
for (i in 0 until length - 1) {
val x: Int = nextInt(length)
val y = array[i]
array[i] = array[i + x]
array[i + x] = y
length--
}
}
fun randomize(array: IntArray) {
var length = array.size
for (i in 0 until length - 1) {
val x: Int = nextInt(length)
val y = array[i]
array[i] = array[i + x]
array[i + x] = y
length--
}
}
fun randomize(list: MutableList<*>) {
var size = list.size
for (i in 0..size) {
val j: Int = nextInt(size)
val obj = list[i]!!
list.set(i, list[i + j] as Nothing)
list.set(i + j, obj as Nothing)
size--
}
}
fun randomize(array: Array<Any?>) {
var length = array.size
for (i in 0 until length - 1) {
val x: Int = nextInt(length)
val y = array[i]
array[i] = array[i + x]
array[i + x] = y
length--
}
}
fun randomize(s: String?): String? {
if (s == null) {
return null
}
val array = s.toCharArray()
randomize(array)
return String(array)
}
companion object {
/**
* 所有大小写字母和数字
*/
val allChar = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
/**
* 所有大小写字母
*/
val letterChar = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
/**
* 所有数字
*/
val numberChar = "0123456789"
/**
* 返回一个定长的随机纯数字字符串(只包含数字)
* @param length
* 随机字符串长度
* @return 随机字符串
*/
fun generateDigitalString(length: Int): String {
val sb = StringBuffer()
val random = Random()
for (i in 0 until length) {
sb.append(numberChar[random.nextInt(numberChar.length)])
}
return sb.toString()
}
/**
* 返回一个定长的随机字符串(只包含大小写字母、数字)
* @param length
* 随机字符串长度
* @return 随机字符串
*/
fun generateString(length: Int): String {
val sb = StringBuffer()
val random = Random()
for (i in 0 until length) {
sb.append(allChar[random.nextInt(allChar.length)])
}
return sb.toString()
}
/**
* 返回一个定长的随机纯字母字符串(只包含大小写字母)
* @param length
* 随机字符串长度
* @return 随机字符串
*/
fun generateMixString(length: Int): String {
val sb = StringBuffer()
val random = Random()
for (i in 0 until length) {
sb.append(letterChar[random.nextInt(letterChar.length)])
}
return sb.toString()
}
/**
* 返回一个定长的随机纯大写字母字符串(只包含大小写字母)
* @param length
* 随机字符串长度
* @return 随机字符串
*/
fun generateLowerString(length: Int): String {
return generateMixString(length).toLowerCase()
}
/**
* 返回一个定长的随机纯小写字母字符串(只包含大小写字母)
* @param length
* 随机字符串长度
* @return 随机字符串
*/
fun generateUpperString(length: Int): String {
return generateMixString(length).toUpperCase()
}
/**
* 生成一个定长的纯0字符串
* @param length
* 字符串长度
* @return 纯0字符串
*/
fun generateZeroString(length: Int): String {
val sb = StringBuffer()
for (i in 0 until length) {
sb.append('0')
}
return sb.toString()
}
/**
* 根据数字生成一个定长的字符串,长度不够前面补0
* @param num
* 数字
* @param fixdlenth
* 字符串长度
* @return 定长的字符串
*/
fun toFixdLengthString(num: Long, fixdlenth: Int): String {
val sb = StringBuffer()
val strNum = num.toString()
if (fixdlenth - strNum.length >= 0) {
sb.append(generateZeroString(fixdlenth - strNum.length))
} else {
throw RuntimeException(
"将数字" + num + "转化为长度为" + fixdlenth
+ "的字符串发生异常!"
)
}
sb.append(strNum)
return sb.toString()
}
/**
* 根据数字生成一个定长的字符串,长度不够前面补0
* @param num
* 数字
* @param fixdlenth
* 字符串长度
* @return 定长的字符串
*/
fun toFixdLengthString(num: Int, fixdlenth: Int): String {
val sb = StringBuffer()
val strNum = num.toString()
if (fixdlenth - strNum.length >= 0) {
sb.append(generateZeroString(fixdlenth - strNum.length))
} else {
throw RuntimeException(
("将数字" + num + "转化为长度为" + fixdlenth
+ "的字符串发生异常!")
)
}
sb.append(strNum)
return sb.toString()
}
/**
* serialVersionUID:TODO(用一句话描述这个变量表示什么)
*
* @since 1.0.0
*/
private val serialVersionUID = 1L
val instance = RandomUtil()
}
}
UserRealm:
/**
-
自定义Realm
-
@author lenovo
/
class UserRealm : AuthorizingRealm() {
/*-
执行授权逻辑
*/
override fun doGetAuthorizationInfo(arg0: PrincipalCollection): AuthorizationInfo {
// println(“执行授权逻辑”)//给资源进行授权
val info = SimpleAuthorizationInfo()//添加资源的授权字符串
// info.addStringPermission(“user:findAll”);//到数据库查询当前登录用户的授权字符串
//获取当前登录用户
val subject = SecurityUtils.getSubject()
val user = subject.principal as User
val dbUser = userSerivce!!.findById(user.id)
info.addStringPermission(dbUser!!.perms)
return info
}
@Autowired
private val userSerivce: UserService? = null/**
-
执行认证逻辑
*/
@Throws(AuthenticationException::class)
override fun doGetAuthenticationInfo(arg0: AuthenticationToken): AuthenticationInfo? {
// println(“执行认证逻辑”)//编写shiro判断逻辑,判断用户名和密码
//1.判断用户名
val token = arg0 as UsernamePasswordToken
val user = userSerivce!!.findByName(token.username) ?: //用户名不存在return null //shiro底层会抛出UnKnowAccountException
//当前realm对象的name
val realmName = name
//盐值
val credentialsSalt: ByteSource = ByteSource.Util.bytes(user.name.toString() + user.salt)
//2.判断密码
//封装用户信息,构建AuthenticationInfo对象并返回
return SimpleAuthenticationInfo(
user, user.password,
credentialsSalt, realmName
)
}
}
@Service
class UserServiceImpl : UserService {
//注入Mapper接口
@Autowired
private val userMapper: UserMapper? = null
override fun findByName(name: String?): User? {
return userMapper!!.findByName(name)
}override fun findById(id: String?): User? {
return userMapper!!.findById(id)
}override fun findAll(id: String?): List? {
return userMapper!!.findAll(id)
}/**
- 注册(加密)
*/
override fun addUser(user: User) {
return userMapper!!.addUser(user)
}
}
test.html 根据数据库里perms判断什么权限显示不同界面
数据库配置(application-dev.properties)
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=CONVERT_TO_NULL&transformedBitIsBoolean=true&allowMultiQueries=true&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
rabbitmq的配置可以去掉但是空格必须和图上一样thymeleaf配置的页面
-
salt是存储的4位随机盐值
项目已上传