springboot整合shiro實現登錄驗證
今天第一次接觸springboot,本來是要學習springboot和shiro整合的,但是由於springboot結構還不太瞭解,所以先來了解一下springboot。
springboot可以快速創建一個機遇spring的項目,而且讓這個項目跑起來只需要很少的配置就可以了,主要有以下核心功能:
1.獨立運行的spring項目:springboot可以以jar包的形式來運行,運行一個springboot項目我們只需要通過jar -jar xx.jar類運行。
2.Spring Boot可以內嵌Tomcat,這樣我們無需以war包的形式部署項目。
3.提供starter簡化Maven配置:使用Spring或者SpringMVC我們需要添加大量的依賴,而這些依賴很多都是固定的,這裏Spring Boot 通過starter能夠幫助我們簡化Maven配置。
4.自動配置Spring 。
5.準生產的應用監控 。
6.無代碼生成和xml配置。
下面就是開始搭建環境了
1、先建一個springboot工程
修改項目group和artifact
選擇所需要的依賴
再選擇一下工作空間就完成了
idea創建springboot項目自帶項目入口,不用自己配置
修改一下入口類,運行就能在瀏覽器裏訪問該項目了,注意要在入口類加上一個@RestController的註解
package com.jiang.springboot_shiro;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@SpringBootApplication
public class SpringbootShiroApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootShiroApplication.class, args);
}
@RequestMapping(value = "/springboot_shiro",produces = "text/plain;charset=UTF-8")//項目訪問URL
public String index(){
return "Hello springboot!";
}
}
現在在瀏覽器裏面輸入localhost:8080/springboot_shiro就能看到返回的Hello springboot!
上面這種方式是把入口類作爲控制類,如果要將控制器和入口類分離,控制器的包一定要跟啓動類是同一目錄,如下:
接下來是整合shiro進行認證,代碼會在之前的代碼上有所改動
pom.xml添加需要的包
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jiang</groupId>
<artifactId>springboot_shiro</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springboot_shiro</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<version>2.0.3.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.18</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
先建一個user表,在user表中加了一列權限
由於我的是集成了mybatis的,所以要先配置springboot的配置文件
application.properties
#spring集成mybatis環境
mybatis.type-aliases-package=com.jiang.springboot_shiro.entity
#加載mybatis配置文件
mybatis.mapper-locations=classpath:mapper/*.xml
#spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/chun?useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=
entity.java
package com.jiang.springboot_shiro.entity;
public class User {
private String name;
private String password;
private String authority;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setPassword(String password) {
this.password = password;
}
public String getPassword() {
return password;
}
public void setAuthority(String authority) {
this.authority = authority;
}
public String getAuthority() {
return authority;
}
}
mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jiang.springboot_shiro.mapper.UserMapper">
<resultMap id="user" type="User">
<result property="name" column="name"></result>
<result property="password" column="password"></result>
</resultMap>
<select id="findUser" resultType="User" resultMap="user">
select * from user where name =#{name} and password=#{password}
</select>
</mapper>
mapper.java
package com.jiang.springboot_shiro.mapper;
import com.jiang.springboot_shiro.entity.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper {
public User findUser(User user);
}
service.java
package com.jiang.springboot_shiro.service;
import com.jiang.springboot_shiro.entity.User;
import com.jiang.springboot_shiro.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public User findUser(User user){
return userMapper.findUser(user);
}
}
controller.java
這裏有要注意的地方,在前面搭建springboot框架的時候註釋是@RestController,這裏要訪問頁面,要把註釋改成@Controller
package com.jiang.springboot_shiro.controller;
import com.jiang.springboot_shiro.entity.User;
import com.jiang.springboot_shiro.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Controller
public class UserController {
/*@RequestMapping("springboot_shiro")//項目訪問URL
public String index(){
return "Hello springboot!";
}*/
@Autowired
private UserService userService;
@RequestMapping("/")
public String getIndex(){
return "login";
}
@RequestMapping("all")
public String getAll(){
return "all";
}
@RequestMapping("one")
public String getOne(){
return "one";
}
@RequestMapping("login")
public String login(){
return "login";
}
@RequestMapping("permission")
public String permission(){
return "permission";
}
@RequestMapping("toLogin")
public String toLogin(User user, Model model){
Subject subject=SecurityUtils.getSubject();
UsernamePasswordToken userToken=new UsernamePasswordToken(user.getName(),user.getPassword());
try{
subject.login(userToken);
return "redirect:/all";
}catch (UnknownAccountException e){
model.addAttribute("msg","用戶名不存在");
return "login";
}catch (IncorrectCredentialsException e){
model.addAttribute("msg","密碼錯誤");
return "login";
}
}
}
HTML頁面就不貼了,只有一個登陸表單,隨便寫個就行了
最重要的是shiro的配置,這裏沒有用配置文件,用了註解
shiroConfig.java
package com.jiang.springboot_shiro.shiro;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Bean(name = "shiroFilterFactoryBean")
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager")DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
Map<String,String> fMap=new HashMap<>();
//攔截頁面
fMap.put("/all","authc");
fMap.put("/one","authc");
//攔截未授權
fMap.put("/all","perms[user:all]");
fMap.put("/one","perms[user:one]");
//被攔截返回登錄頁面
shiroFilterFactoryBean.setLoginUrl("/login");
//授權攔截返回頁面
shiroFilterFactoryBean.setUnauthorizedUrl("/permission");
shiroFilterFactoryBean.setFilterChainDefinitionMap(fMap);
return shiroFilterFactoryBean;
}
@Bean(name = "defaultWebSecurityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm")UserRealm userRealm){
DefaultWebSecurityManager defaultWebSecurityManager=new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(userRealm);
return defaultWebSecurityManager;
}
@Bean(name = "userRealm")
public UserRealm getUserRealm(){
return new UserRealm();
}
}
配置文件裏有一些攔截的配置,anon,authc等,這些還有點糊塗。
shiro最重要的還是Realm類,自定義UserRealm需要繼承AuthorizingRealm類,並重寫其中的doGetAuthenticationInfo方法和doGetAuthorizationInfo方法,其中在controller中調用subject.login(token)時最終是會調用doGetAuthenticationInfo(token)方法,當訪問的頁面需要鑑權的時候會調用doGetAuthorizationInfo(principle)方法。
我自定義的UserRealm.java
package com.jiang.springboot_shiro.shiro;
import com.jiang.springboot_shiro.entity.User;
import com.jiang.springboot_shiro.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0){
System.out.println("授權");
Subject subject=SecurityUtils.getSubject();
User user=(User) subject.getPrincipal();
SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addStringPermission(user.getAuthority());
return simpleAuthorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException {
// TODO Auto-generated method stub
System.out.println("認證");
//shiro判斷邏輯
UsernamePasswordToken user = (UsernamePasswordToken) arg0;
User realUser = new User();
realUser.setName(user.getUsername());
realUser.setPassword(String.copyValueOf(user.getPassword()));
User newUser = userService.findUser(realUser);
//System.out.println(user.getUsername());
if(newUser == null){
//用戶名錯誤
//shiro會拋出UnknownAccountException異常
return null;
}
return new SimpleAuthenticationInfo(newUser,newUser.getPassword(),"");
}
}
這個類還需要改正,因爲這個認證的方法裏,無論如何都只能返回UnknownAccountException異常,controller中永遠都catch不到IncorrectCredentialsException,目前怎麼更正我還沒想好。
因爲沒有配置默認 訪問路徑,只要訪問localhost:8080/就能訪問該項目了,在我的控制器裏面我後來又加上了下面這一段代碼,如果沒有這一段代碼我直接訪問返回的是404,但是這樣程序已經跑起來了,如果輸入localhost:8080/login會發現能夠訪問的。
@RequestMapping("/")
public String getIndex(){
return "login";
}
只是個測試,所以沒寫首頁,直接訪問登錄頁面了。