### 3.2 访问数据库实现用户认证
#### 3.2.1 表结构分析
系统数据库qingcheng_system .tb_admin 表(管理员表)
| 字段名称 | 字段含义 | 字段类型 | 字段长度 | 备注 |
| ---------- | -------- | -------- | -------- | ---- |
| id | id | INT | | |
| login_name | 用户名 | VARCHAR | | |
| password | 密码 | VARCHAR | | |
| status | 状态 | CHAR | | |
#### 3.2.2 代码实现
修改UserDetailsServiceImpl
```java
public class UserDetailsServiceImpl implements UserDetailsService {
@Reference
private AdminService adminService;
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//查询管理员
Map map= new HashMap();
map.put("loginName",s);
map.put("status","1");
List<Admin> list = adminService.findList(map);
if(list.size()==0){
return null;
}
//构建角色集合 ,项目中此处应该是根据用户名查询用户的角色列表
List<GrantedAuthority> grantedAuths = new ArrayList<GrantedAuthority>();
grantedAuths.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
return new User(s,list.get(0).getPassword(), grantedAuths);
}
}
```
## 4. 青橙系统菜单展示
### 4.1 需求分析
从数据库中读取菜单数据并展示。菜单为三级菜单,树形结构如下:
```json
{
"data": [
{
"path": "1",//菜单项所对应的路由路径
"title": "首页", //菜单项名称
"icon":"iconHome",//是否有子菜单,若没有,则为[]
},
{
"path": "2",
"title": "商品",//一级菜单
"icon":"iconCommodity",
"children": [
{
"path": "2-1",
"title": "商品管理",//二级菜单
"linkUrl":"",
"icon":"iconSp",
"children":[
{
"path": "2-1-1",
"title": "商品列表",
"linkUrl":"all-medical-list.html",//三级菜单
},
{
"path": "2-1-2",
"title": "添加商品",
"linkUrl":"commodity-add.html",
}
]
},
{
"path": "2-2",
"title": "添加配置",
"linkUrl":"",
"icon":"iconSet",
"children":[
{
"path": "2-2-1",
"title": "商品分类",
"linkUrl":"all-medical-list.html",
},
{
"path": "2-2-2",
"title": "规格参数",
"linkUrl":"all-medical-list.html",
}
]
}
]
}
]
}
```
### 4.2 表结构分析
tb_menu (菜单表)
| 字段名称 | 字段含义 | 字段类型 | 字段长度 | 备注 |
| --------- | ---------- | -------- | -------- | ---- |
| id | 菜单ID | VARCHAR | | |
| name | 菜单名称 | VARCHAR | | |
| icon | 图标 | VARCHAR | | |
| url | URL | VARCHAR | | |
| parent_id | 上级菜单ID | VARCHAR | | |
### 4.3 代码实现
#### 4.3.1 后端代码
(1)MenuService接口新增方法定义,用于返回全部菜单
```java
public List<Map> findAllMenu();
```
(2)MenuServiceImpl实现此方法
```java
/**
* 查询全部菜单
* @return
*/
public List<Map> findAllMenu() {
List<Menu> menuList = findAll();//查询全部菜单列表
return findMenuListByParentId(menuList,"0");//一级菜单列表
}
/**
* 查询下级菜单ID
* @param menuList
* @param parentId
* @return
*/
private List<Map> findMenuListByParentId(List<Menu> menuList,String parentId){
List<Map> mapList=new ArrayList<>();
for(Menu menu:menuList){ //循环一级菜单
if(menu.getParentId().equals(parentId)){
Map map=new HashMap();
map.put("path",menu.getId());
map.put("title",menu.getName());
map.put("icon",menu.getIcon());
map.put("linkUrl",menu.getUrl());
map.put("children",findMenuListByParentId(menuList,menu.getId()));
mapList.add(map);
}
}
return mapList;
}
```
(3)qingcheng_web_manager工程MenuController新增方法
```java
@GetMapping("/findMenu")
public List<Map> findMenu(){
return menuService.findAllMenu();
}
```
#### 4.3.2 前端代码
修改页面的JS代码
```js
created() {
//.......
axios.get("/menu/findMenu.do").then( response=>{
this.menuList=response.data;
this.data=response.data[0]
var data=[]
for(var i=0;i<response.data[0].children.length;i++){
data.push(response.data[0].children[i].path)
}
this.openeds=data
})
}
```
#### 4.3.3 同源策略设置
由于我们的main.html是框架页,需要修改同源策略。
```xml
<headers>
<frame-options policy="SAMEORIGIN"></frame-options>
</headers>
```
*X-Frame-Options* response header 可用于指示是否应该允许浏览器呈现在一个页面(同源策略)
**同源策略**限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。
解释一下同源。如果两个url,协议、地址和端口都相同,我们称两个url为同源。
Spring Security下,X-Frame-Options默认为DENY. 如果不修改同源策略,框架页内将无法显示内容。
DENY:浏览器拒绝当前页面加载任何Frame页面
SAMEORIGIN:frame页面的地址只能为同源域名下的页面
ALLOW-FROM:origin为允许frame加载的页面地址。
#### 4.3.4 获取当前登录人
需求:主界面显示当前登陆人
实现思路:后端编写controller输出当前登录人,前端异步调用。
(1)后端代码实现:
```java
@RestController
@RequestMapping("/login")
public class LoginController {
@GetMapping("/name")
public Map showName(){
String name = SecurityContextHolder.getContext().getAuthentication().getName();
Map map=new HashMap();
map.put("name",name);
return map;
}
}
```
(2)前端代码实现:
在main.html中添加属性loginName,用于存储当前的登陆人
created()方法添加代码
```js
//加载并显示当前登录名
axios.get('/login/name.do').then( response=>{
this.loginName= response.data.name;
})
```
页面显示登陆人
```
{{loginName}}
```
#### 4.3.5 退出登录
当我们在spring security配置文件中配置了`<logout/>`后,框架会为我们自动提供退出功能,地址为/logout,要求以post方式提交。
main.html新增方法
```js
exit(){
axios.post('/logout').then(response=>{
location.href="login.html";
})
}
```
退出菜单调用:
```html
<span style="display:block;" @click="exit()">退出</span>
```
## 5. 管理员登录日志
### 5.1 需求分析
管理员登录后,记录管理员名称、登录时间、ip、浏览器类型、所在地区等信息 。
### 5.2 表结构分析
tb_login_log 表
| 字段名称 | 字段含义 | 字段类型 | 字段长度 | 备注 |
| ------------ | ---------- | -------- | -------- | ---- |
| id | id | INT | | |
| login_name | 登录名 | VARCHAR | | |
| ip | ip | VARCHAR | | |
| browser_name | 浏览器类型 | VARCHAR | | |
| location | 地区 | VARCHAR | | |
| login_time | 登录时间 | DATETIME | | |
### 5.3 代码实现
#### 5.3.1 登录成功处理器
(1)spring security为我们提供了一个叫“登录成功处理器”的组件,我们可以实现在登录后进行的后续处理逻辑。
```java
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
System.out.println("登录成功处理器到此一游");
request.getRequestDispatcher("/main.html").forward(request, response);
}
}
```
(2)applicationContext_security.xml新增配置
```xml
<beans:bean id="loginHandler" class="com.qingcheng.controller.AuthenticationSuccessHandlerImpl">
</beans:bean>
```
```xml
<!--设置登陆成功处理器-->
<form-login login-page="/login.html"
default-target-url="/main.html"
authentication-failure-url="/login.html"
authentication-success-handler-ref="loginHandler"/>
```
#### 5.3.2 登录日志处理
修改qingcheng_web_manager的AuthenticationSuccessHandlerImpl
```java
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {
@Reference
private LoginLogService loginLogService;
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
//登录后会调用
System.out.println("登录成功了,我要在这里记录日志");
String loginName = authentication.getName();//当前登录用户
String ip = httpServletRequest.getRemoteAddr(); //获取访问的ip
LoginLog loginLog=new LoginLog();
loginLog.setLoginName(loginName);
loginLog.setLoginTime(new Date()); // 当前登录时间
loginLog.setIp(ip);
loginLogService.add(loginLog); httpServletRequest.getRequestDispatcher("/main.html").forward(httpServletRequest,httpServletResponse);
}
}
```
#### 5.3.3 根据IP获取城市信息
(1)将资源中提供的工具类WebUtil复制到qingcheng_common_web工程
(2)WebUtil类的getCityByIP用于根据IP地址获取城市信息,修改代码
```java
loginLog.setLocation(WebUtil.getCityByIP(ip));//保存城市信息
```
#### 5.3.4 获取浏览器名称 |
|