2017.2.16 开涛shiro教程-第十七章-OAuth2集成(一)服务器端
原博客地址:http://jinnianshilongnian.iteye.com/blog/2018398
根据下载的pdf学习。
开涛shiro教程-第十七章-OAuth2集成
1.OAuth2介绍
(1)应用场景
很多开放平台,比如新浪微博开放平台,都在使用开发API接口供开发者使用。即带来了,第三方应用要到开放平台授权的问题。OAuth就是做这个的。
OAuth2官网:http://oauth.net/2/
OAuth2协议:http://tools.ietf.org/html/rfc6749
本文使用:Apache Oltu
使用文档:https://cwiki.apache.org/confluence/display/OLTU/Documentation
(2)OAuth角色
资源拥有者resource owner:能授权访问受保护资源的一个实体。比如新浪微博用户lyh。
资源服务器resource server:存储受保护资源。
授权服务器authorization server:成功验证resource owner,并获取授权,颁发授权令牌access token给客户端client。
客户端client:本身不存储资源,而是resource owner授权通过后,使用access token访问受保护资源,然后把相应的数据展示/提交到服务器。
(3)OAuth2协议流程
2.服务器端
(1)POM依赖
此处我们使用 apache oltu oauth2 服务端实现,需要引入 authzserver(授权服务器依赖)和 resourceserver(资源服务器依赖)。
<dependency>
<groupId>org.apache.oltu.oauth2</groupId>
<artifactId>org.apache.oltu.oauth2.authzserver</artifactId>
<version>0.31</version>
</dependency> <dependency>
<groupId>org.apache.oltu.oauth2</groupId>
<artifactId>org.apache.oltu.oauth2.resourceserver</artifactId>
<version>0.31</version>
</dependency>
附完整pom.xml文件:
<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/maven-v4_0_0.xsd">
<parent>
<artifactId>shiro-example</artifactId>
<groupId>com.github.zhangkaitao</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>shiro-example-chapter17-server</artifactId>
<packaging>war</packaging>
<name>shiro-example-chapter17-server</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency> <dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency> <dependency>
<groupId>org.apache.oltu.oauth2</groupId>
<artifactId>org.apache.oltu.oauth2.authzserver</artifactId>
<version>0.31</version>
</dependency> <dependency>
<groupId>org.apache.oltu.oauth2</groupId>
<artifactId>org.apache.oltu.oauth2.resourceserver</artifactId>
<version>0.31</version>
</dependency> <dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency> <dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-quartz</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.2</version>
</dependency> <dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.25</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>0.2.23</version>
</dependency> <!-- aspectj相关jar包-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.7.4</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.7.4</version>
</dependency> <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.0.0.RELEASE</version>
</dependency> <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.0.0.RELEASE</version>
</dependency> <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.0.0.RELEASE</version>
</dependency> <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.0.0.RELEASE</version>
</dependency> <!--jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.2.3</version>
</dependency> </dependencies>
<build>
<finalName>chapter17-server</finalName>
<plugins>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>8.1.8.v20121106</version>
<configuration>
<webAppConfig>
<contextPath>/${project.build.finalName}</contextPath>
</webAppConfig>
</configuration>
</plugin> <plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<path>/${project.build.finalName}</path>
</configuration> </plugin>
</plugins> </build>
</project>
pom.xml
(2)table
shiro-schema.sql
oauth2_user存储着resource owner,oauth2_client存储着client的信息,在进行授权时使用。
drop table if exists oauth2_client;
drop table if exists oauth2_user; create table oauth2_user (
id bigint auto_increment,
username varchar(100),
password varchar(100),
salt varchar(100),
constraint pk_oauth2_user primary key(id)
) charset=utf8 ENGINE=InnoDB;
create unique index idx_oauth2_user_username on oauth2_user(username); create table oauth2_client (
id bigint auto_increment,
client_name varchar(100),
client_id varchar(100),
client_secret varchar(100),
constraint pk_oauth2_client primary key(id)
) charset=utf8 ENGINE=InnoDB;
create index idx_oauth2_client_client_id on oauth2_client(client_id);
shiro-data.sql:
DELIMITER ;
delete from oauth2_user;
delete from oauth2_client; insert into oauth2_user values(1,'admin','d3c59d25033dbf980d29554025c23a75','8d78869f470951332959580424d4bf4f');
insert into oauth2_client values(1,'chapter17-client','c1ebe466-1cdc-4bd3-ab69-77c3561b9dee','d8346ea2-6017-43ed-ad68-19c0f971738b');
(2)entity
package com.github.zhangkaitao.shiro.chapter17.entity; import java.io.Serializable; /**
* <p>User: Zhang Kaitao
* <p>Date: 14-2-17
* <p>Version: 1.0
*/
public class Client implements Serializable { private Long id;
private String clientName;
private String clientId;
private String clientSecret; public Long getId() {
return id;
} public void setId(Long id) {
this.id = id;
} public String getClientName() {
return clientName;
} public void setClientName(String clientName) {
this.clientName = clientName;
} public String getClientId() {
return clientId;
} public void setClientId(String clientId) {
this.clientId = clientId;
} public String getClientSecret() {
return clientSecret;
} public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
} @Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; Client client = (Client) o; if (id != null ? !id.equals(client.id) : client.id != null) return false; return true;
} @Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
} @Override
public String toString() {
return "Client{" +
"id=" + id +
", clientName='" + clientName + '\'' +
", clientId='" + clientId + '\'' +
", clientSecret='" + clientSecret + '\'' +
'}';
}
}
Client
package com.github.zhangkaitao.shiro.chapter17.entity; import java.io.Serializable; /**
* <p>User: Zhang Kaitao
* <p>Date: 14-2-17
* <p>Version: 1.0
*/
public class User implements Serializable {
private Long id; //编号
private String username; //用户名
private String password; //密码
private String salt; //加密密码的盐 public Long getId() {
return id;
} public void setId(Long id) {
this.id = id;
} public String getUsername() {
return username;
} public void setUsername(String username) {
this.username = username;
} public String getPassword() {
return password;
} public void setPassword(String password) {
this.password = password;
} public String getSalt() {
return salt;
} public void setSalt(String salt) {
this.salt = salt;
} public String getCredentialsSalt() {
return username + salt;
} @Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; User user = (User) o; if (id != null ? !id.equals(user.id) : user.id != null) return false; return true;
} @Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
} @Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", salt='" + salt + '\'' +
'}';
}
}
User
(3)dao
public interface ClientDao { public Client createClient(Client client);
public Client updateClient(Client client);
public void deleteClient(Long clientId); Client findOne(Long clientId); List<Client> findAll(); Client findByClientId(String clientId);
Client findByClientSecret(String clientSecret); }
ClientDao
package com.github.zhangkaitao.shiro.chapter17.dao; import com.github.zhangkaitao.shiro.chapter17.entity.Client;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.stereotype.Repository; import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List; /**
* <p>User: Zhang Kaitao
* <p>Date: 14-1-28
* <p>Version: 1.0
*/
@Repository
public class ClientDaoImpl implements ClientDao { @Autowired
private JdbcTemplate jdbcTemplate; public Client createClient(final Client client) {
final String sql = "insert into oauth2_client(client_name, client_id, client_secret) values(?,?,?)"; GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(new PreparedStatementCreator() {
@Override
public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
PreparedStatement psst = connection.prepareStatement(sql, new String[]{"id"});
int count = 1;
psst.setString(count++, client.getClientName());
psst.setString(count++, client.getClientId());
psst.setString(count++, client.getClientSecret());
return psst;
}
}, keyHolder); client.setId(keyHolder.getKey().longValue());
return client;
} public Client updateClient(Client client) {
String sql = "update oauth2_client set client_name=?, client_id=?, client_secret=? where id=?";
jdbcTemplate.update(
sql,
client.getClientName(), client.getClientId(), client.getClientSecret(), client.getId());
return client;
} public void deleteClient(Long clientId) {
String sql = "delete from oauth2_client where id=?";
jdbcTemplate.update(sql, clientId);
} @Override
public Client findOne(Long clientId) {
String sql = "select id, client_name, client_id, client_secret from oauth2_client where id=?";
List<Client> clientList = jdbcTemplate.query(sql, new BeanPropertyRowMapper(Client.class), clientId);
if(clientList.size() == 0) {
return null;
}
return clientList.get(0);
} @Override
public List<Client> findAll() {
String sql = "select id, client_name, client_id, client_secret from oauth2_client";
return jdbcTemplate.query(sql, new BeanPropertyRowMapper(Client.class));
} @Override
public Client findByClientId(String clientId) {
String sql = "select id, client_name, client_id, client_secret from oauth2_client where client_id=?";
List<Client> clientList = jdbcTemplate.query(sql, new BeanPropertyRowMapper(Client.class), clientId);
if(clientList.size() == 0) {
return null;
}
return clientList.get(0);
} @Override
public Client findByClientSecret(String clientSecret) {
String sql = "select id, client_name, client_id, client_secret from oauth2_client where client_secret=?";
List<Client> clientList = jdbcTemplate.query(sql, new BeanPropertyRowMapper(Client.class), clientSecret);
if(clientList.size() == 0) {
return null;
}
return clientList.get(0);
}
}
ClientDaoImpl
public interface UserDao { public User createUser(User user);
public User updateUser(User user);
public void deleteUser(Long userId); User findOne(Long userId); List<User> findAll(); User findByUsername(String username); }
UserDao
package com.github.zhangkaitao.shiro.chapter17.dao; import com.github.zhangkaitao.shiro.chapter17.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.stereotype.Repository; import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List; @Repository
public class UserDaoImpl implements UserDao { @Autowired
private JdbcTemplate jdbcTemplate; public User createUser(final User user) {
final String sql = "insert into oauth2_user(username, password, salt) values(?,?,?)"; GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(new PreparedStatementCreator() {
@Override
public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
PreparedStatement psst = connection.prepareStatement(sql, new String[]{"id"});
int count = 1;
psst.setString(count++, user.getUsername());
psst.setString(count++, user.getPassword());
psst.setString(count++, user.getSalt());
return psst;
}
}, keyHolder); user.setId(keyHolder.getKey().longValue());
return user;
} public User updateUser(User user) {
String sql = "update oauth2_user set username=?, password=?, salt=? where id=?";
jdbcTemplate.update(
sql,
user.getUsername(), user.getPassword(), user.getSalt(), user.getId());
return user;
} public void deleteUser(Long userId) {
String sql = "delete from oauth2_user where id=?";
jdbcTemplate.update(sql, userId);
} @Override
public User findOne(Long userId) {
String sql = "select id, username, password, salt from oauth2_user where id=?";
List<User> userList = jdbcTemplate.query(sql, new BeanPropertyRowMapper(User.class), userId);
if(userList.size() == 0) {
return null;
}
return userList.get(0);
} @Override
public List<User> findAll() {
String sql = "select id, username, password, salt from oauth2_user";
return jdbcTemplate.query(sql, new BeanPropertyRowMapper(User.class));
} @Override
public User findByUsername(String username) {
String sql = "select id, username, password, salt from oauth2_user where username=?";
List<User> userList = jdbcTemplate.query(sql, new BeanPropertyRowMapper(User.class), username);
if(userList.size() == 0) {
return null;
}
return userList.get(0);
}
}
UserDaoImpl
(4)service
public interface ClientService { public Client createClient(Client client);
public Client updateClient(Client client);
public void deleteClient(Long clientId); Client findOne(Long clientId); List<Client> findAll(); Client findByClientId(String clientId);
Client findByClientSecret(String clientSecret); }
ClientService
@Transactional
@Service
public class ClientServiceImpl implements ClientService {
@Autowired
private ClientDao clientDao; @Override
public Client createClient(Client client) { client.setClientId(UUID.randomUUID().toString());
client.setClientSecret(UUID.randomUUID().toString());
return clientDao.createClient(client);
} @Override
public Client updateClient(Client client) {
return clientDao.updateClient(client);
} @Override
public void deleteClient(Long clientId) {
clientDao.deleteClient(clientId);
} @Override
public Client findOne(Long clientId) {
return clientDao.findOne(clientId);
} @Override
public List<Client> findAll() {
return clientDao.findAll();
} @Override
public Client findByClientId(String clientId) {
return clientDao.findByClientId(clientId);
} @Override
public Client findByClientSecret(String clientSecret) {
return clientDao.findByClientSecret(clientSecret);
}
}
ClientServiceImpl
public interface UserService { public User createUser(User user);
public User updateUser(User user);
public void deleteUser(Long userId); public void changePassword(Long userId, String newPassword); User findOne(Long userId);
List<User> findAll();
public User findByUsername(String username); }
UserService
@Transactional
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Autowired
private PasswordHelper passwordHelper; public User createUser(User user) {
//加密密码
passwordHelper.encryptPassword(user);
return userDao.createUser(user);
} @Override
public User updateUser(User user) {
return userDao.updateUser(user);
} @Override
public void deleteUser(Long userId) {
userDao.deleteUser(userId);
} public void changePassword(Long userId, String newPassword) {
User user =userDao.findOne(userId);
user.setPassword(newPassword);
passwordHelper.encryptPassword(user);
userDao.updateUser(user);
} @Override
public User findOne(Long userId) {
return userDao.findOne(userId);
} @Override
public List<User> findAll() {
return userDao.findAll();
} public User findByUsername(String username) {
return userDao.findByUsername(username);
} }
UserServiceImpl
@Service
public class PasswordHelper { private RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator(); @Value("${password.algorithmName}")
private String algorithmName = "md5";
@Value("${password.hashIterations}")
private int hashIterations = 2; public void setRandomNumberGenerator(RandomNumberGenerator randomNumberGenerator) {
this.randomNumberGenerator = randomNumberGenerator;
} public void setAlgorithmName(String algorithmName) {
this.algorithmName = algorithmName;
} public void setHashIterations(int hashIterations) {
this.hashIterations = hashIterations;
} public void encryptPassword(User user) { user.setSalt(randomNumberGenerator.nextBytes().toHex()); String newPassword = new SimpleHash(
algorithmName,
user.getPassword(),
ByteSource.Util.bytes(user.getCredentialsSalt()),
hashIterations).toHex(); user.setPassword(newPassword);
}
}
PasswordHelper
通过 OAuthService 实现进行 auth code 和 access token 的维护。
public interface OAuthService { //添加 auth code
public void addAuthCode(String authCode, String username);
//添加 access token
public void addAccessToken(String accessToken, String username); //验证auth code是否有效
boolean checkAuthCode(String authCode);
//验证access token是否有效
boolean checkAccessToken(String accessToken); String getUsernameByAuthCode(String authCode);
String getUsernameByAccessToken(String accessToken); //auth code / access token 过期时间
long getExpireIn(); public boolean checkClientId(String clientId);
public boolean checkClientSecret(String clientSecret); }
@Service
public class OAuthServiceImpl implements OAuthService { private Cache cache; @Autowired
private ClientService clientService; @Autowired
public OAuthServiceImpl(CacheManager cacheManager) {
this.cache = cacheManager.getCache("code-cache");
} @Override
public void addAuthCode(String authCode, String username) {
cache.put(authCode, username);
} @Override
public void addAccessToken(String accessToken, String username) {
cache.put(accessToken, username);
} @Override
public String getUsernameByAuthCode(String authCode) {
return (String)cache.get(authCode).get();
} @Override
public String getUsernameByAccessToken(String accessToken) {
return (String)cache.get(accessToken).get();
} @Override
public boolean checkAuthCode(String authCode) {
return cache.get(authCode) != null;
} @Override
public boolean checkAccessToken(String accessToken) {
return cache.get(accessToken) != null;
} @Override
public boolean checkClientId(String clientId) {
return clientService.findByClientId(clientId) != null;
} @Override
public boolean checkClientSecret(String clientSecret) {
return clientService.findByClientSecret(clientSecret) != null;
} @Override
public long getExpireIn() {
return 3600L;
}
}
OAuthServiceImpl
(5)Controller
om.github.zhangkaitao.shiro.chapter17.web.controller 包下的 IndexController、LoginController、UserController 和 ClientController,其用于维护后端的数据,如用户及客户端数据;即相当于后台管理。
@Controller
public class IndexController { @RequestMapping("/")
public String index(Model model) {
return "index";
}
}
IndexController
@Controller
public class LoginController { @RequestMapping(value = "/login")
public String showLoginForm(HttpServletRequest req, Model model) {
String exceptionClassName = (String)req.getAttribute("shiroLoginFailure");
String error = null;
if(UnknownAccountException.class.getName().equals(exceptionClassName)) {
error = "用户名/密码错误";
} else if(IncorrectCredentialsException.class.getName().equals(exceptionClassName)) {
error = "用户名/密码错误";
} else if(exceptionClassName != null) {
error = "其他错误:" + exceptionClassName;
}
model.addAttribute("error", error);
return "login";
}
}
LoginController
@Controller
@RequestMapping("/user")
public class UserController { @Autowired
private UserService userService; @RequestMapping(method = RequestMethod.GET)
public String list(Model model) {
model.addAttribute("userList", userService.findAll());
return "user/list";
} @RequestMapping(value = "/create", method = RequestMethod.GET)
public String showCreateForm(Model model) {
model.addAttribute("user", new User());
model.addAttribute("op", "新增");
return "user/edit";
} @RequestMapping(value = "/create", method = RequestMethod.POST)
public String create(User user, RedirectAttributes redirectAttributes) {
userService.createUser(user);
redirectAttributes.addFlashAttribute("msg", "新增成功");
return "redirect:/user";
} @RequestMapping(value = "/{id}/update", method = RequestMethod.GET)
public String showUpdateForm(@PathVariable("id") Long id, Model model) {
model.addAttribute("user", userService.findOne(id));
model.addAttribute("op", "修改");
return "user/edit";
} @RequestMapping(value = "/{id}/update", method = RequestMethod.POST)
public String update(User user, RedirectAttributes redirectAttributes) {
userService.updateUser(user);
redirectAttributes.addFlashAttribute("msg", "修改成功");
return "redirect:/user";
} @RequestMapping(value = "/{id}/delete", method = RequestMethod.GET)
public String showDeleteForm(@PathVariable("id") Long id, Model model) {
model.addAttribute("user", userService.findOne(id));
model.addAttribute("op", "删除");
return "user/edit";
} @RequestMapping(value = "/{id}/delete", method = RequestMethod.POST)
public String delete(@PathVariable("id") Long id, RedirectAttributes redirectAttributes) {
userService.deleteUser(id);
redirectAttributes.addFlashAttribute("msg", "删除成功");
return "redirect:/user";
} @RequestMapping(value = "/{id}/changePassword", method = RequestMethod.GET)
public String showChangePasswordForm(@PathVariable("id") Long id, Model model) {
model.addAttribute("user", userService.findOne(id));
model.addAttribute("op", "修改密码");
return "user/changePassword";
} @RequestMapping(value = "/{id}/changePassword", method = RequestMethod.POST)
public String changePassword(@PathVariable("id") Long id, String newPassword, RedirectAttributes redirectAttributes) {
userService.changePassword(id, newPassword);
redirectAttributes.addFlashAttribute("msg", "修改密码成功");
return "redirect:/user";
} }
UserController
@Controller
@RequestMapping("/client")
public class ClientController { @Autowired
private ClientService clientService; @RequestMapping(method = RequestMethod.GET)
public String list(Model model) {
model.addAttribute("clientList", clientService.findAll());
return "client/list";
} @RequestMapping(value = "/create", method = RequestMethod.GET)
public String showCreateForm(Model model) {
model.addAttribute("client", new Client());
model.addAttribute("op", "新增");
return "client/edit";
} @RequestMapping(value = "/create", method = RequestMethod.POST)
public String create(Client client, RedirectAttributes redirectAttributes) {
clientService.createClient(client);
redirectAttributes.addFlashAttribute("msg", "新增成功");
return "redirect:/client";
} @RequestMapping(value = "/{id}/update", method = RequestMethod.GET)
public String showUpdateForm(@PathVariable("id") Long id, Model model) {
model.addAttribute("client", clientService.findOne(id));
model.addAttribute("op", "修改");
return "client/edit";
} @RequestMapping(value = "/{id}/update", method = RequestMethod.POST)
public String update(Client client, RedirectAttributes redirectAttributes) {
clientService.updateClient(client);
redirectAttributes.addFlashAttribute("msg", "修改成功");
return "redirect:/client";
} @RequestMapping(value = "/{id}/delete", method = RequestMethod.GET)
public String showDeleteForm(@PathVariable("id") Long id, Model model) {
model.addAttribute("client", clientService.findOne(id));
model.addAttribute("op", "删除");
return "client/edit";
} @RequestMapping(value = "/{id}/delete", method = RequestMethod.POST)
public String delete(@PathVariable("id") Long id, RedirectAttributes redirectAttributes) {
clientService.deleteClient(id);
redirectAttributes.addFlashAttribute("msg", "删除成功");
return "redirect:/client";
} }
ClientController
授权控制器:AuthorizeController
@Controller
public class AuthorizeController { @Autowired
private OAuthService oAuthService;
@Autowired
private ClientService clientService; @RequestMapping("/authorize")
public Object authorize(
Model model,
HttpServletRequest request)
throws URISyntaxException, OAuthSystemException { try {
//构建OAuth 授权请求
OAuthAuthzRequest oauthRequest = new OAuthAuthzRequest(request); //检查传入的客户端id是否正确
if (!oAuthService.checkClientId(oauthRequest.getClientId())) {
OAuthResponse response =
OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST)
.setError(OAuthError.TokenResponse.INVALID_CLIENT)
.setErrorDescription(Constants.INVALID_CLIENT_DESCRIPTION)
.buildJSONMessage();
return new ResponseEntity(response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
} Subject subject = SecurityUtils.getSubject();
//如果用户没有登录,跳转到登陆页面
if(!subject.isAuthenticated()) {
if(!login(subject, request)) {//登录失败时跳转到登陆页面
model.addAttribute("client", clientService.findByClientId(oauthRequest.getClientId()));
return "oauth2login";
}
} String username = (String)subject.getPrincipal();
//生成授权码
String authorizationCode = null;
//responseType目前仅支持CODE,另外还有TOKEN
String responseType = oauthRequest.getParam(OAuth.OAUTH_RESPONSE_TYPE);
if (responseType.equals(ResponseType.CODE.toString())) {
OAuthIssuerImpl oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator());
authorizationCode = oauthIssuerImpl.authorizationCode();
oAuthService.addAuthCode(authorizationCode, username);
} //进行OAuth响应构建
OAuthASResponse.OAuthAuthorizationResponseBuilder builder =
OAuthASResponse.authorizationResponse(request, HttpServletResponse.SC_FOUND);
//设置授权码
builder.setCode(authorizationCode);
//得到到客户端重定向地址
String redirectURI = oauthRequest.getParam(OAuth.OAUTH_REDIRECT_URI); //构建响应
final OAuthResponse response = builder.location(redirectURI).buildQueryMessage(); //根据OAuthResponse返回ResponseEntity响应
HttpHeaders headers = new HttpHeaders();
headers.setLocation(new URI(response.getLocationUri()));
return new ResponseEntity(headers, HttpStatus.valueOf(response.getResponseStatus()));
} catch (OAuthProblemException e) { //出错处理
String redirectUri = e.getRedirectUri();
if (OAuthUtils.isEmpty(redirectUri)) {
//告诉客户端没有传入redirectUri直接报错
return new ResponseEntity("OAuth callback url needs to be provided by client!!!", HttpStatus.NOT_FOUND);
} //返回错误消息(如?error=)
final OAuthResponse response =
OAuthASResponse.errorResponse(HttpServletResponse.SC_FOUND)
.error(e).location(redirectUri).buildQueryMessage();
HttpHeaders headers = new HttpHeaders();
headers.setLocation(new URI(response.getLocationUri()));
return new ResponseEntity(headers, HttpStatus.valueOf(response.getResponseStatus()));
}
} private boolean login(Subject subject, HttpServletRequest request) {
if("get".equalsIgnoreCase(request.getMethod())) {
return false;
}
String username = request.getParameter("username");
String password = request.getParameter("password"); if(StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
return false;
} UsernamePasswordToken token = new UsernamePasswordToken(username, password); try {
subject.login(token);
return true;
} catch (Exception e) {
request.setAttribute("error", "登录失败:" + e.getClass().getName());
return false;
}
}
}
访问令牌控制器:AccessTokenController
@RestController
public class AccessTokenController { @Autowired
private OAuthService oAuthService; @Autowired
private UserService userService; @RequestMapping("/accessToken")
public HttpEntity token(HttpServletRequest request)
throws URISyntaxException, OAuthSystemException { try {
//构建OAuth请求
OAuthTokenRequest oauthRequest = new OAuthTokenRequest(request); //检查提交的客户端id是否正确
if (!oAuthService.checkClientId(oauthRequest.getClientId())) {
OAuthResponse response =
OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST)
.setError(OAuthError.TokenResponse.INVALID_CLIENT)
.setErrorDescription(Constants.INVALID_CLIENT_DESCRIPTION)
.buildJSONMessage();
return new ResponseEntity(response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
} // 检查客户端安全KEY是否正确
if (!oAuthService.checkClientSecret(oauthRequest.getClientSecret())) {
OAuthResponse response =
OAuthASResponse.errorResponse(HttpServletResponse.SC_UNAUTHORIZED)
.setError(OAuthError.TokenResponse.UNAUTHORIZED_CLIENT)
.setErrorDescription(Constants.INVALID_CLIENT_DESCRIPTION)
.buildJSONMessage();
return new ResponseEntity(response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
} String authCode = oauthRequest.getParam(OAuth.OAUTH_CODE);
// 检查验证类型,此处只检查AUTHORIZATION_CODE类型,其他的还有PASSWORD或REFRESH_TOKEN
if (oauthRequest.getParam(OAuth.OAUTH_GRANT_TYPE).equals(GrantType.AUTHORIZATION_CODE.toString())) {
if (!oAuthService.checkAuthCode(authCode)) {
OAuthResponse response = OAuthASResponse
.errorResponse(HttpServletResponse.SC_BAD_REQUEST)
.setError(OAuthError.TokenResponse.INVALID_GRANT)
.setErrorDescription("错误的授权码")
.buildJSONMessage();
return new ResponseEntity(response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
}
} //生成Access Token
OAuthIssuer oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator());
final String accessToken = oauthIssuerImpl.accessToken();
oAuthService.addAccessToken(accessToken, oAuthService.getUsernameByAuthCode(authCode)); //生成OAuth响应
OAuthResponse response = OAuthASResponse
.tokenResponse(HttpServletResponse.SC_OK)
.setAccessToken(accessToken)
.setExpiresIn(String.valueOf(oAuthService.getExpireIn()))
.buildJSONMessage(); //根据OAuthResponse生成ResponseEntity
return new ResponseEntity(response.getBody(), HttpStatus.valueOf(response.getResponseStatus())); } catch (OAuthProblemException e) {
//构建错误响应
OAuthResponse res = OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST).error(e)
.buildJSONMessage();
return new ResponseEntity(res.getBody(), HttpStatus.valueOf(res.getResponseStatus()));
}
} }
资源控制器:UserInfoController
@RestController
public class UserInfoController { @Autowired
private OAuthService oAuthService; @RequestMapping("/userInfo")
public HttpEntity userInfo(HttpServletRequest request) throws OAuthSystemException {
try { //构建OAuth资源请求
OAuthAccessResourceRequest oauthRequest = new OAuthAccessResourceRequest(request, ParameterStyle.QUERY);
//获取Access Token
String accessToken = oauthRequest.getAccessToken(); //验证Access Token
if (!oAuthService.checkAccessToken(accessToken)) {
// 如果不存在/过期了,返回未验证错误,需重新验证
OAuthResponse oauthResponse = OAuthRSResponse
.errorResponse(HttpServletResponse.SC_UNAUTHORIZED)
.setRealm(Constants.RESOURCE_SERVER_NAME)
.setError(OAuthError.ResourceResponse.INVALID_TOKEN)
.buildHeaderMessage(); HttpHeaders headers = new HttpHeaders();
headers.add(OAuth.HeaderType.WWW_AUTHENTICATE, oauthResponse.getHeader(OAuth.HeaderType.WWW_AUTHENTICATE));
return new ResponseEntity(headers, HttpStatus.UNAUTHORIZED);
}
//返回用户名
String username = oAuthService.getUsernameByAccessToken(accessToken);
return new ResponseEntity(username, HttpStatus.OK);
} catch (OAuthProblemException e) {
//检查是否设置了错误码
String errorCode = e.getError();
if (OAuthUtils.isEmpty(errorCode)) {
OAuthResponse oauthResponse = OAuthRSResponse
.errorResponse(HttpServletResponse.SC_UNAUTHORIZED)
.setRealm(Constants.RESOURCE_SERVER_NAME)
.buildHeaderMessage(); HttpHeaders headers = new HttpHeaders();
headers.add(OAuth.HeaderType.WWW_AUTHENTICATE, oauthResponse.getHeader(OAuth.HeaderType.WWW_AUTHENTICATE));
return new ResponseEntity(headers, HttpStatus.UNAUTHORIZED);
} OAuthResponse oauthResponse = OAuthRSResponse
.errorResponse(HttpServletResponse.SC_UNAUTHORIZED)
.setRealm(Constants.RESOURCE_SERVER_NAME)
.setError(e.getError())
.setErrorDescription(e.getDescription())
.setErrorUri(e.getUri())
.buildHeaderMessage(); HttpHeaders headers = new HttpHeaders();
headers.add(OAuth.HeaderType.WWW_AUTHENTICATE, oauthResponse.getHeader(OAuth.HeaderType.WWW_AUTHENTICATE));
return new ResponseEntity(HttpStatus.BAD_REQUEST);
}
}
}
(6)配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:property-placeholder location="classpath:resources.properties"/> <!-- 扫描注解Bean -->
<context:component-scan base-package="com.github.zhangkaitao.shiro.chapter17">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan> <!-- 开启AOP监听 只对当前配置文件有效 -->
<aop:aspectj-autoproxy expose-proxy="true"/> <!-- 数据源 -->
<!--see https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_DruidDataSource%E5%8F%82%E8%80%83%E9%85%8D%E7%BD%AE-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 基本属性 url、user、password -->
<property name="url" value="${connection.url}"/>
<property name="username" value="${connection.username}"/>
<property name="password" value="${connection.password}"/> <!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="${druid.initialSize}"/>
<property name="minIdle" value="${druid.minIdle}"/>
<property name="maxActive" value="${druid.maxActive}"/> <!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="${druid.maxWait}"/>
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="${druid.timeBetweenEvictionRunsMillis}" /> <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="${druid.minEvictableIdleTimeMillis}" /> <property name="validationQuery" value="${druid.validationQuery}" />
<property name="testWhileIdle" value="${druid.testWhileIdle}" />
<property name="testOnBorrow" value="${druid.testOnBorrow}" />
<property name="testOnReturn" value="${druid.testOnReturn}" /> <!-- 打开PSCache,并且指定每个连接上PSCache的大小 如果用Oracle,则把poolPreparedStatements配置为true,mysql可以配置为false。-->
<property name="poolPreparedStatements" value="${druid.poolPreparedStatements}" />
<property name="maxPoolPreparedStatementPerConnectionSize" value="${druid.maxPoolPreparedStatementPerConnectionSize}" /> <!-- 配置监控统计拦截的filters -->
<property name="filters" value="${druid.filters}" /> </bean> <bean id="dataSourceProxy" class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy">
<property name="targetDataSource" ref="dataSource"/>
</bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg ref="dataSourceProxy"/>
</bean> <!--事务管理器配置-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSourceProxy"/>
</bean> <tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice> <aop:config expose-proxy="true" proxy-target-class="true">
<!-- 只对业务逻辑层实施事务 -->
<aop:pointcut id="txPointcut" expression="execution(* com.github.zhangkaitao.shiro.chapter16..service..*+.*(..))"/>
<aop:advisor id="txAdvisor" advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config> <import resource="classpath:spring-config-cache.xml"/>
<import resource="classpath:spring-config-shiro.xml"/>
</beans>
spring-config.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <context:property-placeholder location="classpath:resources.properties"/> <!-- 开启controller注解支持 -->
<!-- 注意事项请参考:http://jinnianshilongnian.iteye.com/blog/1762632 -->
<context:component-scan base-package="com.github.zhangkaitao.**.web.controller" use-default-filters="false">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
<context:include-filter type="annotation"
expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
</context:component-scan> <mvc:annotation-driven>
</mvc:annotation-driven> <!-- 当在web.xml 中 DispatcherServlet使用 <url-pattern>/</url-pattern> 映射时,能映射静态资源 -->
<mvc:default-servlet-handler/> <!-- 静态资源映射 -->
<mvc:resources mapping="/static/**" location="/WEB-INF/static/"/> <!-- 默认的视图解析器 在上边的解析错误时使用 (默认使用html)- -->
<bean id="defaultViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:order="1">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="contentType" value="text/html"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean> <!-- 控制器异常处理 -->
<bean id="exceptionHandlerExceptionResolver" class="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver">
</bean> <bean class="com.github.zhangkaitao.shiro.chapter17.web.exception.DefaultExceptionHandler"/> <import resource="spring-mvc-shiro.xml"/> </beans>
spring-mvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <aop:config proxy-target-class="true"></aop:config>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean> </beans>
spring-mvc-shiro.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 缓存管理器 -->
<bean id="cacheManager" class="com.github.zhangkaitao.shiro.spring.SpringCacheManagerWrapper">
<property name="cacheManager" ref="springCacheManager"/>
</bean> <!-- 凭证匹配器 -->
<bean id="credentialsMatcher" class="com.github.zhangkaitao.shiro.chapter17.credentials.RetryLimitHashedCredentialsMatcher">
<constructor-arg ref="cacheManager"/>
<property name="hashAlgorithmName" value="md5"/>
<property name="hashIterations" value="2"/>
<property name="storedCredentialsHexEncoded" value="true"/>
</bean> <!-- Realm实现 -->
<bean id="userRealm" class="com.github.zhangkaitao.shiro.chapter17.realm.UserRealm">
<property name="credentialsMatcher" ref="credentialsMatcher"/>
<property name="cachingEnabled" value="false"/>
<!--<property name="authenticationCachingEnabled" value="true"/>-->
<!--<property name="authenticationCacheName" value="authenticationCache"/>-->
<!--<property name="authorizationCachingEnabled" value="true"/>-->
<!--<property name="authorizationCacheName" value="authorizationCache"/>-->
</bean> <!-- 会话ID生成器 -->
<bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/> <!-- 会话Cookie模板 -->
<bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg value="sid"/>
<property name="httpOnly" value="true"/>
<property name="maxAge" value="-1"/>
</bean> <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg value="rememberMe"/>
<property name="httpOnly" value="true"/>
<property name="maxAge" value="2592000"/><!-- 30天 -->
</bean> <!-- rememberMe管理器 -->
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<!-- rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)-->
<property name="cipherKey"
value="#{T(org.apache.shiro.codec.Base64).decode('4AvVhmFLUs0KTA3Kprsdag==')}"/>
<property name="cookie" ref="rememberMeCookie"/>
</bean> <!-- 会话DAO -->
<bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">
<property name="activeSessionsCacheName" value="shiro-activeSessionCache"/>
<property name="sessionIdGenerator" ref="sessionIdGenerator"/>
</bean> <!-- 会话验证调度器 -->
<bean id="sessionValidationScheduler" class="org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler">
<property name="sessionValidationInterval" value="1800000"/>
<property name="sessionManager" ref="sessionManager"/>
</bean> <!-- 会话管理器 -->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="globalSessionTimeout" value="1800000"/>
<property name="deleteInvalidSessions" value="true"/>
<property name="sessionValidationSchedulerEnabled" value="true"/>
<property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>
<property name="sessionDAO" ref="sessionDAO"/>
<property name="sessionIdCookieEnabled" value="true"/>
<property name="sessionIdCookie" ref="sessionIdCookie"/>
</bean> <!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="userRealm"/>
<property name="sessionManager" ref="sessionManager"/>
<property name="cacheManager" ref="cacheManager"/>
<property name="rememberMeManager" ref="rememberMeManager"/>
</bean> <!-- 相当于调用SecurityUtils.setSecurityManager(securityManager) -->
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/>
<property name="arguments" ref="securityManager"/>
</bean> <!-- 基于Form表单的身份验证过滤器 -->
<bean id="formAuthenticationFilter" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">
<property name="usernameParam" value="username"/>
<property name="passwordParam" value="password"/>
<property name="rememberMeParam" value="rememberMe"/>
<property name="loginUrl" value="/login"/>
</bean> <!-- Shiro的Web过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login"/>
<property name="filters">
<util:map>
<entry key="authc" value-ref="formAuthenticationFilter"/>
</util:map>
</property>
<property name="filterChainDefinitions">
<value>
/ = anon
/login = authc
/logout = logout /authorize=anon
/accessToken=anon
/userInfo=anon /** = user
</value>
</property>
</bean> <!-- Shiro生命周期处理器-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> </beans>
spring-config-shiro.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="springCacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
<property name="cacheManager" ref="ehcacheManager"/>
</bean> <!--ehcache-->
<bean id="ehcacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation" value="classpath:ehcache/ehcache.xml"/>
</bean> </beans>
spring-config-cache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="es"> <diskStore path="java.io.tmpdir"/> <!-- 登录记录缓存 锁定10分钟 -->
<cache name="passwordRetryCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache> <cache name="authorizationCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache> <cache name="authenticationCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache> <cache name="shiro-activeSessionCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache> <cache name="code-cache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache> </ehcache>
ehcache.xml
#dataSource configure
connection.url=jdbc:mysql://localhost:3306/shiro-oauth2
connection.username=root
connection.password= #druid datasource
#参考 https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_DruidDataSource%E5%8F%82%E8%80%83%E9%85%8D%E7%BD%AE
druid.initialSize=10
druid.minIdle=10
druid.maxActive=50
druid.maxWait=60000
druid.timeBetweenEvictionRunsMillis=60000
druid.minEvictableIdleTimeMillis=300000
druid.validationQuery=SELECT 'x'
druid.testWhileIdle=true
druid.testOnBorrow=false
druid.testOnReturn=false
druid.poolPreparedStatements=true
druid.maxPoolPreparedStatementPerConnectionSize=20
druid.filters=wall,stat #shiro
password.algorithmName=md5
password.hashIterations=2
resource.properties
(7)服务器的维护
访问 localhost:8080/chapter17-server/,登录后进行客户端管理和用户管理。
客户端管理就是进行客户端的注册,如新浪微博的第三方应用就需要到新浪微博开发平台进行注册;
用户管理就是进行如新浪微博用户的管理。
3.客户端
客户端流程可以参照如很多网站的新浪微博登录功能,或其他的第三方帐号登录功能。
具体请看《2017.2.16 开涛shiro教程-第十七章-OAuth2集成(一)客户端》
2017.2.16 开涛shiro教程-第十七章-OAuth2集成(一)服务器端的更多相关文章
- 2017.2.16 开涛shiro教程-第十七章-OAuth2集成(二)客户端
原博客地址:http://jinnianshilongnian.iteye.com/blog/2018398 根据下载的pdf学习. 开涛shiro教程-第十七章-OAuth2集成 3.客户端 客户端 ...
- 2017.2.15 开涛shiro教程-第二十一章-授予身份与切换身份(二) controller
原博客地址:http://jinnianshilongnian.iteye.com/blog/2018398 根据下载的pdf学习. 开涛shiro教程-第二十一章-授予身份与切换身份(二) 1.回顾 ...
- 2017.2.15 开涛shiro教程-第二十一章-授予身份与切换身份(一) table、entity、service、dao
原博客地址:http://jinnianshilongnian.iteye.com/blog/2018398 根据下载的pdf学习. 第二十一章 授予身份与切换身份(一) 1.使用场景 某个领导因为某 ...
- 2017.2.12 开涛shiro教程-第七章-与Web集成
2017.2.9 开涛shiro教程-第七章-与Web集成(一) 原博客地址:http://jinnianshilongnian.iteye.com/blog/2018398 根据下载的pdf学习. ...
- 2017.2.7 开涛shiro教程-第六章-Realm及相关对象(四)
原博客地址:http://jinnianshilongnian.iteye.com/blog/2018398 根据下载的pdf学习. 第六章 Realm及相关对象(四) 1.Subject的代码结构 ...
- 2017.2.7 开涛shiro教程-第六章-Realm及相关对象(二)
原博客地址:http://jinnianshilongnian.iteye.com/blog/2018398 根据下载的pdf学习. 第六章 Realm及相关对象(二) 1.Authenticatio ...
- 2017.2.7 开涛shiro教程-第六章-Realm及相关对象(一)
原博客地址:http://jinnianshilongnian.iteye.com/blog/2018398 根据下载的pdf学习. 第六章 Realm及相关对象 1.用户.角色.权限的关系 用户和角 ...
- 2017.2.7 开涛shiro教程-第六章-Realm及相关对象(三)
原博客地址:http://jinnianshilongnian.iteye.com/blog/2018398 根据下载的pdf学习. 第六章 Realm及相关对象(三) 1.准备3个Realm MyR ...
- 2017.4.12 开涛shiro教程-第十八章-并发登录人数控制
原博客地址:http://jinnianshilongnian.iteye.com/blog/2018398 根据下载的pdf学习. 开涛shiro教程-第十八章-并发登录人数控制 shiro中没有提 ...
随机推荐
- Spring框架的AOP
Spring学习笔记(四) 本文目录 1 AOP的介绍 2 Spring的AspectJ实现AOP(annotation) 3 Spring的AspectJ实现AOP (XML) Spring文档ht ...
- bable
Babel是一个javascrpt编译器,能将es6转换为es5代码.并且通过插件的形式可以灵活的扩展. 从根目录读取.babelrc文件中读取配置..babelrc是一个json文件 配置 plug ...
- jQuery操作DOM基础 - 元素属性的查看与设置
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- Knights of the Round Table
Knights of the Round Table Being a knight is a very attractive career: searching for the Holy Grail, ...
- vue的v-for循环普通数组、对象数组、对象、数字
如下 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8& ...
- css3上下翻页效果
翻页效果显示当前时间 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> &l ...
- VMware Esxi5.5中嵌套虚拟机的网络设置方法
环境: Esxi5.5服务器->虚拟机(WinServer2008R2)->VMware WorkStation(Win7虚拟机) 网络问题: VMware WorkStation中的虚拟 ...
- .net web api ioc unity usage
1.use nuget to install unity.webapi 2.add configurations in application_start folder using Microsoft ...
- AVRStudio 6 设置F_CPU时钟频率
具体如下: 1>右键项目属性 2>根据语言选择一下,C或C++
- nslookup命令详解【转】
转自:http://blog.chinaunix.net/uid-28933499-id-3750357.html NSlookup命令的用法 用了域名服务器后,经常要查询域名的解析情况,nslook ...