springboot+swagger+shiro+ssm的前后分离
思路:用户登录的认证逻辑我们自己写,用户请求资源的认证交给shiro
用户认证登录的逻辑: 首先前端传过来用户名和密码,通过用户名查询用户,如果查询到,比较密码,如果一致,则生成一个token(这个token作为用户唯一的身份标识)返回给前端,并将这个token保存在数据库中
用户请求资源的逻辑
首先,自定义一个过滤器,使用login方法,这里的login传参仅仅是我们的token,因此,我们要重写token,调用login方法以后-----realm 我们将前端传过来的token值封装并返回(源码中认证最后一步比较的是密码,我们换成比较token),然后重写HashedCredentialsMatcher 中的doCredentialsMatch方法,从数据库中获取到token值,两个进行比较,如果相等,则认证通过,否则,认证失败
1.导包
<dependencies>
<!--导入支持json的包-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.68</version>
</dependency>
<!--导入shiro相关的包-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!--导入swagger相关的包-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<!--导入druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.20</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<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>2.1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
2.编写全局配置
mybatis.type-aliases-package=com.wcc.springboot.dao
mybatis.mapper-locations=classpath:mapping/*.xml
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.password=root
spring.datasource.username=root
spring.datasource.url=jdbc:mysql://localhost:3306/mydb_01?\
useUnicode=true&useJDBCCompliantTimezoneShift=true&\
useLegacyDatetimeCode=false&serverTimezone=UTC
3. 编写实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable{
private static final long serialVersionUID = -491147926030118870L;
private Integer id;
private String name;
private String password;
private String token;
private Date expiredData;//token的过期时间
}
4. 编写自定义异常
@Data
@NoArgsConstructor
public class BaseException extends RuntimeException {
private int code;
private String msg;
public BaseException(int code,String msg){
super(msg);
this.code=code;
this.msg=msg;
}
public String getMsg(){
return msg;
}
public int getCode(){
return code;
}
}
5.编写token
public class CustomToken extends UsernamePasswordToken {
private String token;
public CustomToken(String token){
this.token=token;
}
@Override
public Object getPrincipal () {
return token;
}
}
6.编写realm
public class CustomRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo ( PrincipalCollection principalCollection ) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo ( AuthenticationToken authenticationToken ) throws AuthenticationException {
CustomToken customToken =(CustomToken)authenticationToken;
String token = (String) customToken.getPrincipal();
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(token, token, getName());
return authenticationInfo;
}
}
7.编写认证器
public class CustomCredential extends HashedCredentialsMatcher {
@Autowired
private UserService userService;
@Override
public boolean doCredentialsMatch ( AuthenticationToken token, AuthenticationInfo info ) {
CustomToken customToken = (CustomToken)token;
// 获取前端传递过来的token
String frontToken = (String) customToken.getPrincipal();
// 看数据库中是否存在这个token,如果存在,则返回true
boolean b = false;
try {
b = userService.isExsit(frontToken);
}catch (BaseException e){
throw new BaseException(30004,"查询失败");
}
if (!b){
throw new BaseException(30005,"授权信息无效");
}
return true;
}
}
8. 编写过滤器
public class CustomFilter extends AccessControlFilter {
@Override
protected boolean isAccessAllowed ( ServletRequest servletRequest, ServletResponse servletResponse, Object o ) throws Exception {
return false;
}
// 过滤器,每一次身份校验的时候,都会执行到这里
@Override
protected boolean onAccessDenied ( ServletRequest servletRequest, ServletResponse servletResponse ) throws Exception {
HttpServletRequest request = (HttpServletRequest)servletRequest;
try {
// 校验身份,首先获取前端传过来的token
// 将前端的token名设置在一个常量类中
String frontToken = request.getHeader(Constant.RE_TOKEN);
// 判断token是否为空,如果为空,说明用户身份是不合法的
if (StringUtils.isEmpty(frontToken)){
throw new BaseException(40001,"用户请求的token不能为空");
}else{
// 将token进行封装,封装完成之后,交给shiro进行验证,看身份认证是否合法
CustomToken customToken = new CustomToken(frontToken);
// 下面这个方法是用户登陆成功以后,在访问其他资源的时候,做身份校验的
// 这个方法在用户第一次登录的时候,并不会执行
getSubject(servletRequest,servletResponse).login(customToken);
}
} catch (BaseException e) {
resultResponse(e.getCode(),e.getMsg(),servletResponse);
return false;
} catch (AuthenticationException e) {// 校验没通过的异常
if(e.getCause() instanceof BaseException){
BaseException err = (BaseException)e.getCause();
resultResponse(err.getCode(),err.getMsg(),servletResponse);
}else{ //说明是shiro的异常
resultResponse(30001,"用户校验失败",servletResponse);
}
}catch (AuthorizationException e){//校验权限
if (e.getCause() instanceof BaseException){
BaseException err = (BaseException)e.getCause();
resultResponse(err.getCode(),err.getMsg(),servletResponse);
}else{
resultResponse(30002,"用户没有权限",servletResponse);
}
}catch (Exception e){//表示的一些未考虑的异常
if (e.getCause() instanceof BaseException){
BaseException err = (BaseException)e.getCause();
resultResponse(err.getCode(),err.getMsg(),servletResponse);
}else{
resultResponse(30003,"系统异常",servletResponse);
}
}
return true;
}
/**
* 此方法返回的是异常的结果,告诉前端的一些消息
*/
public void resultResponse(int code,String msg,ServletResponse response){
// 构建返回的数据
JSONObject jsonObject = new JSONObject();
jsonObject.put("code",code);
jsonObject.put("msg",msg);
// 设置返回的数据类型
response.setContentType(String.valueOf(MediaType.APPLICATION_JSON_UTF8));
// 获取输出流
try {
ServletOutputStream outputStream = response.getOutputStream();
outputStream.write(jsonObject.toJSONString().getBytes());
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
9. 编写配置文件
9.1 全局配置
@SpringBootConfiguration
@ComponentScan(basePackages = {"com.wcc.springboot"})
@MapperScan(basePackages = {"com.wcc.springboot.mapper"})
public class AppConfig {
}
9.2 swagger配置
@SpringBootConfiguration
@EnableSwagger2
public class SwaeggerConfig {
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.wcc.springboot.controller"))
.paths(PathSelectors.any())
.build();
}
public ApiInfo apiInfo(){
return new ApiInfoBuilder()
.version("V1.0")
.title("shiro+swagger+前后分离")
.description("这是shiro整合swagger的前后分离测试")
.contact("laowang")
.build();
}
}
9.3 shiro配置
@SpringBootConfiguration
public class ShiroConfig {
// 配置过滤器
@Bean
public ShiroFilterFactoryBean filterFactoryBean(){
ShiroFilterFactoryBean shiroFilterFactoryBean =
new ShiroFilterFactoryBean();
// 配置自己的过滤器
Map<String,Filter> map = new LinkedHashMap<>();
map.put("token",new CustomFilter());
shiroFilterFactoryBean.setFilters(map);
// 配置对请求拦截和过滤的设置
Map<String,String> map1 = new LinkedHashMap<>();
// 登陆页面不拦截
map1.put("/toLogin","anon");
// swagger的所有请求资源和请求地址都不拦截
map1.put("/swagger/**","anon");
map1.put("/v2/api-docs","anon");
map1.put("/swagger-ui.html","anon");
map1.put("/swagger-resources/**","anon");
map1.put("/webjars/**","anon");
map1.put("/favicon.ico","anon");
map1.put("/captcha.jpg","anon");
map1.put("/csrf","anon");
// 设置我们自己的校验
map1.put("/**","token,authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map1);
shiroFilterFactoryBean.setSecurityManager(securityManager());
return shiroFilterFactoryBean;
}
// 配置安全管理器
@Bean
public DefaultWebSecurityManager securityManager(){
DefaultWebSecurityManager securityManager =
new DefaultWebSecurityManager();
securityManager.setRealm(customRealm());
return securityManager;
}
// 配置我们的realm
@Bean
public CustomRealm customRealm(){
CustomRealm customRealm = new CustomRealm();
customRealm.setCredentialsMatcher(customCredential());
return customRealm;
}
// 配置凭证匹配器
@Bean
public CustomCredential customCredential(){
CustomCredential customCredential = new CustomCredential();
return customCredential;
}
// 开启基于注解的权限校验
@Bean
public AuthorizationAttributeSourceAdvisor sourceAdvisor(){
AuthorizationAttributeSourceAdvisor advisor =
new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager());
return advisor;
}
@Bean
public DefaultAdvisorAutoProxyCreator proxyCreator(){
DefaultAdvisorAutoProxyCreator proxyCreator = new
DefaultAdvisorAutoProxyCreator();
proxyCreator.setProxyTargetClass(true);
return proxyCreator;
}
}
10. 编写常量类
public class Constant {
public static final String RE_TOKEN="token";
}
11. 编写mapper
public interface UserMapper {
User findUserByName( String name);
void updateToken( User userByName );
User findToken( String frontToken );
List<User> findAll();
}
12. 编写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.wcc.springboot.mapper.UserMapper">
<select id="findUserByName" resultType="com.wcc.springboot.dao.User">
select * from user WHERE name=#{name};
</select>
<update id="updateToken">
UPDATE user SET token=#{token} WHERE id=#{id}
</update>
<select id="findToken" resultType="com.wcc.springboot.dao.User">
select * from user WHERE token = #{frontToken} ;
</select>
<select id="findAll" resultType="com.wcc.springboot.dao.User">
SELECT * FROM user;
</select>
</mapper>
13. 编写service
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public User findByName ( String name ) {
User user = userMapper.findUserByName(name);
return user;
}
@Override
public User login ( User user ) {
String name = user.getName();
User userByName = userMapper.findUserByName(name);
if (userByName==null){
throw new BaseException(40000,"用户名不存在");
}
if (!userByName.getPassword().equals(user.getPassword())){
throw new BaseException(40001,"密码错误");
}
String token = UUID.randomUUID().toString();
Date date = new Date();
userByName.setToken(token);
userByName.setExpiredData(date);
// 设置token和过期时间以后,更新数据库
updateToken(userByName);
// 将密码设置为空,然后将用户对象返回给前端
userByName.setPassword("");
return userByName;
}
public void updateToken ( User userByName ) {
userMapper.updateToken(userByName);
}
@Override
public boolean isExsit ( String frontToken ) {
User user = userMapper.findToken(frontToken);
if (user==null){
return false;
}
return true;
}
@Override
public List<User> findAll () {
List<User> users = userMapper.findAll();
return users;
}
}
14. 编写controller
@RestController
@Api(tags = {"usercontroller测试"})
public class UserController {
@Autowired
private UserService userService;
@RequestMapping(value = "toLogin",method = RequestMethod.POST)
@ApiOperation(value = "用户登录的接口测试")
public DataResult<User> toLogin( User user){
DataResult<User> dataResult = null;
try {
User user1 = userService.login(user);
dataResult = new DataResult<User>(
BaseCodeAndMsg.SUCCESS.getCode(),
BaseCodeAndMsg.SUCCESS.getMsg(),
user1
);
} catch (Exception e) {
if (e instanceof BaseException){
BaseException error = (BaseException) e;
dataResult = new DataResult<User>(error.getCode(),error.getMsg(),null);
}else {
dataResult = new
DataResult<User>(BaseCodeAndMsg.SYSTEEM_ERROR.getCode(),
BaseCodeAndMsg.SYSTEEM_ERROR.getMsg(),null);
}
return dataResult;
}
return dataResult;
}
@RequestMapping(value = "getAll",method = RequestMethod.POST)
@ApiOperation(value = "获取所有用户信息的接口")
@ApiImplicitParam(paramType = "header",name="token",required = true,dataType = "String")
public DataResult<List<User>> getAllUser(){
DataResult<List<User>> dataResult;
try {
List<User> users = userService.findAll();
dataResult = new DataResult<List<User>>(BaseCodeAndMsg.SUCCESS.getCode(),
BaseCodeAndMsg.SUCCESS.getMsg(),users);
} catch (Exception e) {
dataResult = new DataResult<List<User>>(BaseCodeAndMsg.SYSTEEM_ERROR.getCode(),
BaseCodeAndMsg.SYSTEEM_ERROR.getMsg(),null);
}
return dataResult;
}
}
15.编写封装结果集
15.1 定义结果集的接口
public interface CodeAndMsg {
int getCode();
String getMsg();
}
15.2 定义枚举实现接口
public enum BaseCodeAndMsg implements CodeAndMsg {
SUCCESS(0,"操作成功"),
SYSTEEM_ERROR(50001,"系统异常"),
INVALIDATE_ERROR(40003,"数据校验异常"),
TOKEN_ERROR(40001,"用户校验异常"),
DATA_ERROR(40000,"数据传入异常"),
AUTHORIZATION_REEOR(40004,"用户权限不够,无法访问")
;
private int code;
private String msg;
BaseCodeAndMsg(int code,String msg){
this.code = code;
this.msg = msg;
}
@Override
public int getCode () {
return code;
}
@Override
public String getMsg () {
return msg;
}
}
15.3 定义返回给前端的结果集
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DataResult<T> {
private Integer code;
private String msg;
private T data;
public DataResult(CodeAndMsg codeAndMsg){
this.code = codeAndMsg.getCode();
this.msg = codeAndMsg.getMsg();
}
public DataResult(CodeAndMsg codeAndMsg,T data){
this.code = codeAndMsg.getCode();
this.msg = codeAndMsg.getMsg();
this.data = data;
}
}