第1章 用户授权
1.1 角色从数据库加载
1.1.1 修改自定义授权认证类
修改自定义认证类,代码如下:
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//通过账号查询用户信息
SysUser sysUser = userDao.getByUserName(username);
if(sysUser!=null){
// 先设置假的权限
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
//获取用户角色
List<SysRole> roles = sysUser.getRoles();
for (SysRole role : roles) {
// 传入角色
authorities.add(new SimpleGrantedAuthority(role.getRoleName()));
}
// 创建用户,这和用户是springsecurity中的User
//User user = new User(username, "{noop}"+sysUser.getPassword(), authorities) ;
User user = new User(username, sysUser.getPassword(), authorities) ;
return user;
}
return null;
}
1.1.2 修改UserDao
直接修改Dao,通过@Many调用com.itheima.dao.RoleDao.getByUserId查询用户的角色信息。
/***
* 查询用户信息
* @param name
* @return
*/
@Select("select * from sys_user where username=#{username}")
@Results(
//根据用户信息查询角色信息
@Result(property ="roles",column = "id",
many = @Many(select = "com.itheima.dao.RoleDao.getByUserId",
fetchType = FetchType.LAZY))
)
SysUser getByUserName(String name);
1.2 access属性的配置
access的值是一个字符串,其可以直接是一个权限的定义,也可以是一个表达式。常用的类型有简单的角色名称定义,多个名称之间用逗号分隔。
<security:intercept-url pattern="/**" access="ROLE_USER,ROLE_ADMIN"/>
在上述配置中就表示secure路径下的所有URL请求都应当具有ROLE_USER或ROLE_ADMIN权限。当access的值是以“ROLE_”开头的则将会交由RoleVoter进行处理。
此外,其还可以是一个表达式
use-expressions="true" <security:intercept-url pattern="/secure/**" access="hasAnyRole('ROLE_USER','ROLE_ADMIN')"/>
或者是使用hasRole()表达式,然后中间以or连接
<security:intercept-url pattern="/secure/**" access="hasRole('ROLE_USER') or hasRole('ROLE_ADMIN')"/>
匿名访问配置:IS_AUTHENTICATED_ANONYMOUSLY表示用户不需要登录就可以访问;
第2章 用户模块
2.1 获取用户名
2.1.1 Session信息分析
我们使用SpringSecurity实现对用户的访问拦截操作,如果用户没有登录,那么SpringSecurity会让用户进行登录,但是目前为止,我们还从未获取过登录用户信息,用户信息会存储到哪里呢?根据我们以往的开发经验,一般会将用户信息存入Session,我们可以在用户登录之后将Session输出。
修改main.jsp,在内容区域处添加session信息
<!-- 内容区域 -->
<div class="content-wrapper">
${sessionScope}
</div>
<!-- 内容区域 /-->
用z1用户登录,session信息如下:
{SPRING_SECURITY_CONTEXT=org.springframework.security.core.context.SecurityContextImpl@bbe395ff:
Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@bbe395ff:
Principal: org.springframework.security.core.userdetails.User@ef7: Username: z1; Password: [PROTECTED];
Enabled: true;
AccountNonExpired: true;
credentialsNonExpired: true;
AccountNonLocked: true;
Granted Authorities: ROLE_USER;
Credentials: [PROTECTED];
Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@fffe9938: RemoteIpAddress: 0:0:0:0:0:0:0:1;
SessionId: BA5BC6E21D04DEDEA4A539AD6B1205F1;
Granted Authorities: ROLE_USER}
Session信息中有一个key叫SPRING_SECURITY_CONTEXT,它所对应的类是SecurityContextImpl,这个类是SecurityContext接口的实现类,它里面包含一个对象Authentication,这个接口的实现类是UsernamePasswordAuthenticationToken,这个类里有个对象Principal,而这个对象就是User,这个User也就是每次我们登录封装的用户信息对象,里面包含了用户名,用户密码和授权信息。
按照这个分析,我们可以直接从session中在页面取出用户信息,取法如下:
<!-- 内容区域 -->
<div class="content-wrapper">
${sessionScope.SPRING_SECURITY_CONTEXT.authentication.principal.username}
</div>
2.1.2 SpringSecurity标签获取用户信息
SpringSecurity也提供了对应的标签来获取用户信息,不过我们得先导入对应的标签包。
在ssm-parent中引入
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>${spring.security.version}</version>
</dependency>
在ssm-web中引入
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
</dependency>
在页面引入(header.jsp)
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>
<security:authentication property="principal.username" />
2.1.3 服务器端后台对象封装的分析
- 对象的封装,认证通过后会返回User对象,该对象中包含用户名等信息。
- principal(主角)就是User对象。
- 框架会把principal封装到Authentication(认证)对象中
- Authentication对象会封装到SecurityContext(Security上下文对象)中
- 最后会把SecurityContext绑定到当前的线程中
后台获取用户信息代码:
/***
* 先获取到SecurityContext对象
*/
SecurityContext securityContext = SecurityContextHolder.getContext();
//获取认证对象
Authentication authentication = securityContext.getAuthentication();
//获取用户登录信息
User user = (User) authentication.getPrincipal();
System.out.println(user.getUsername());
2.1 页面权限控制
2.1.1 开启SpELl表达式
这里需要先开启SpELl表达式,否则不能使用SpringSecurity页面对应的标签。
设置use-expressions="true",access修改:access="hasAnyRole('ROLE_USER','ROLE_ADMIN')"
<security:http auto-config="true" use-expressions="true">
<!-- 配置拦截的请求地址,任何请求地址都必须有ROLE_USER的权限 -->
<security:intercept-url pattern="/index.jsp" access="ROLE_USER" />
<security:intercept-url pattern="/**" access="hasAnyRole('ROLE_USER','ROLE_ADMIN')" />
//略....
</security:http>
2.1.2 页面使用security:authorize
<security:authorize access="hasAnyRole('ROLE_ADMIN','ROLE_USER')">
<li class="treeview"><a href="#"> <i class="fa fa-cogs"></i>
<span>系统管理</span> <span class="pull-right-container"> <i
class="fa fa-angle-left pull-right"></i>
</span>
</a>
<ul class="treeview-menu">
<li id="system-setting"><a
href="${pageContext.request.contextPath}/user/list"> <i
class="fa fa-circle-o"></i> 用户管理
</a></li>
<li id="system-setting"><a
href="${pageContext.request.contextPath}/role/list"> <i
class="fa fa-circle-o"></i> 角色管理
</a></li>
<security:authorize access="hasAnyRole('ROLE_ADMIN')">
<li id="system-setting"><a
href="${pageContext.request.contextPath}/permisson/list">
<i class="fa fa-circle-o"></i> 权限管理
</a></li>
<li id="system-setting"><a
href="${pageContext.request.contextPath}/pages/syslog-list.jsp"> <i
class="fa fa-circle-o"></i> 访问日志
</a></li>
</security:authorize>
</ul></li>
</security:authorize>
2.2 在服务器端控制权限
通过上面的控制,我们发现一个问题,用户只是页面看不到菜单按钮,但让然能访问原来地址,我们需要做后台权限控制。这里提供了多种注解方式实现,我们着重讲讲其中3种注解实现方式。但无论哪种方式,都需要首先开启AOP支持,并且不能多种注解同时使用。修改SpringMVC配置文件如下:
<!--开启AOP-->
<aop:aspectj-autoproxy proxy-target-class="true" />
2.2.1 @RolesAllowed
使用该注解需要首先引入依赖:
在pom.xml中引入:
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>jsr250-api</artifactId>
<version>1.0</version>
</dependency>
在spring-security.xml配置文件中开启JSR-250的注解支持
<security:global-method-security jsr250-annotations="enabled"/>
在Controller的类或者方法上添加注解
@RolesAllowed("ROLE_ADMIN")
public class RoleController {}
2.2.2 security注解方式权限拦截:@Secured
在spring-security.xml配置文件中开启注解支持
<security:global-method-security secured-annotations="enabled"/>
在Controller类或者方法添加注解
@Secured("ROLE_ADMIN")
public class RoleController{}
2.2.3 Spring表达式的方式
在spring-security.xml配置文件中开启注解支持
<security:global-method-security pre-post-annotations="enabled"/>
在Controller类或者方法添加注解
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
public class RoleController {}
2.3 页面403处理
在用户没有权限访问时,经常会出现403页面,不是很友好。SpringSecurity对这个也有控制,只需要在spring-security.xml中加上如下配置即可。
<security:access-denied-handler error-page="/403.jsp"/>
第3章 日志功能
3.1 搭建日志开发环境
创建日志表结构
--日志表
CREATE TABLE `sys_log` (
`id` int(10) NOT NULL,
`visitTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`username` varchar(50) DEFAULT NULL,
`ip` varchar(30) DEFAULT NULL,
`method` varchar(200) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
同时为表结构创建SysLog实体Bean
public class SysLog {
private Long id;
private Date visitTime;
private String username;
private String ip;
private String method;
//get...set...
}
创建SysLogDao接口
public interface SysLogDao {
/**
* 增加日志
* @param sysLog
*/
@Insert("insert into sys_log(visitTime,username,ip,method)values(#{visitTime},#{username},#{ip},#{method})")
void addLog(SysLog sysLog);
}
创建SysLogService接口
public interface SysLogService {
void addLog(SysLog sysLog);
}
创建SysLogServiceImpl实现类
@Service
public class SysLogServiceImpl implements SysLogService {
@Autowired
private SysLogDao sysLogDao;
@Override
public void addLog(SysLog sysLog) {
sysLogDao.addLog(sysLog);
}
}
3.2 AOP日志操作类
由于需要记录用户IP,所以需要创建RequestContextListener监听,它会把每次用户请求注入到SpringIOC容器中。
在web.xml中添加监听器
<!-- 配置监听器,监听request域对象的创建和销毁的 -->
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
创建LogHandler类
@Aspect
@Component
public class LoggerHandler {
@Autowired
private SysLogService sysLogService;
@Autowired
private HttpServletRequest request;
/***
* 前置通知日志操作
* @param jp
*/
@Before(value = "execution(* com.itheima.controller.*Controller.*(..))")
public void logBefore(JoinPoint jp){
addLog(jp,"Before");
}
/***
* 后置通知日志操作
* @param jp
*/
@After(value = "execution(* com.itheima.controller.*Controller.*(..))")
public void logAfter(JoinPoint jp){
addLog(jp,"After");
}
/***
* 添加日志
* @param jp
*/
public void addLog(JoinPoint jp,String adviceType){
//获取用户的IP
String ip = request.getRemoteAddr();
//方法名字
String methodName = jp.getSignature().getName();
Class clazz = jp.getSignature().getDeclaringType();
SysLog sysLog = new SysLog();
sysLog.setIp(ip);
sysLog.setMethod(adviceType+":"+clazz+"."+methodName);
sysLog.setUsername(getUserName());
sysLog.setVisitTime(new Date());
//添加日志操作
sysLogService.addLog(sysLog);
}
/***
* 获取用户名字
* @return
*/
public String getUserName(){
return SecurityContextHolder.getContext().getAuthentication().getName();
}
}